diff options
Diffstat (limited to 'editeng/source/editeng/impedit3.cxx')
-rw-r--r-- | editeng/source/editeng/impedit3.cxx | 1946 |
1 files changed, 1172 insertions, 774 deletions
diff --git a/editeng/source/editeng/impedit3.cxx b/editeng/source/editeng/impedit3.cxx index 9281f4d7dcf2..72bc2022b1ea 100644 --- a/editeng/source/editeng/impedit3.cxx +++ b/editeng/source/editeng/impedit3.cxx @@ -24,6 +24,7 @@ #include <vcl/settings.hxx> #include <vcl/window.hxx> +#include <editeng/outliner.hxx> #include <editeng/tstpitem.hxx> #include <editeng/lspcitem.hxx> #include <editeng/flditem.hxx> @@ -41,18 +42,24 @@ #include <editeng/wghtitem.hxx> #include <editeng/postitem.hxx> #include <editeng/langitem.hxx> +#include <editeng/frmdiritem.hxx> #include <editeng/scriptspaceitem.hxx> #include <editeng/charscaleitem.hxx> #include <editeng/numitem.hxx> +#include <outleeng.hxx> +#include <TextPortion.hxx> #include <svtools/colorcfg.hxx> #include <svl/ctloptions.hxx> #include <svl/asiancfg.hxx> +#include <svx/compatflags.hxx> +#include <sfx2/viewsh.hxx> + #include <editeng/hngpnctitem.hxx> #include <editeng/forbiddencharacterstable.hxx> -#include <unotools/configmgr.hxx> +#include <comphelper/configuration.hxx> #include <math.h> #include <vcl/metric.hxx> @@ -63,23 +70,27 @@ #include <i18nlangtag/mslangid.hxx> #include <comphelper/processfactory.hxx> +#include <comphelper/lok.hxx> #include <rtl/ustrbuf.hxx> #include <sal/log.hxx> #include <o3tl/safeint.hxx> #include <o3tl/sorted_vector.hxx> #include <osl/diagnose.h> #include <comphelper/string.hxx> +#include <cstddef> #include <memory> #include <set> #include <vcl/outdev/ScopedStates.hxx> +#include <unicode/uchar.h> + using namespace ::com::sun::star; using namespace ::com::sun::star::uno; using namespace ::com::sun::star::beans; using namespace ::com::sun::star::linguistic2; -constexpr OUStringLiteral CH_HYPH = u"-"; +constexpr OUString CH_HYPH = u"-"_ustr; constexpr tools::Long WRONG_SHOW_MIN = 5; @@ -105,42 +116,22 @@ struct TabInfo } -Point Rotate( const Point& rPoint, Degree10 nOrientation, const Point& rOrigin ) -{ - double nRealOrientation = nOrientation.get() * F_PI1800; - double nCos = cos( nRealOrientation ); - double nSin = sin( nRealOrientation ); - - Point aRotatedPos; - Point aTranslatedPos( rPoint ); - - // Translation - aTranslatedPos -= rOrigin; - - // Rotation... - aRotatedPos.setX( static_cast<tools::Long>( nCos*aTranslatedPos.X() + nSin*aTranslatedPos.Y() ) ); - aRotatedPos.setY( static_cast<tools::Long>(- ( nSin*aTranslatedPos.X() - nCos*aTranslatedPos.Y() )) ); - aTranslatedPos = aRotatedPos; - - // Translation... - aTranslatedPos += rOrigin; - return aTranslatedPos; -} - AsianCompressionFlags GetCharTypeForCompression( sal_Unicode cChar ) { switch ( cChar ) { case 0x3008: case 0x300A: case 0x300C: case 0x300E: case 0x3010: case 0x3014: case 0x3016: case 0x3018: - case 0x301A: case 0x301D: + case 0x301A: case 0x301D: case 0xFF09: case 0xFF3D: + case 0xFF5D: { return AsianCompressionFlags::PunctuationRight; } case 0x3001: case 0x3002: case 0x3009: case 0x300B: case 0x300D: case 0x300F: case 0x3011: case 0x3015: case 0x3017: case 0x3019: case 0x301B: case 0x301E: - case 0x301F: + case 0x301F: case 0xFF08: case 0xFF0C: case 0xFF0E: + case 0xFF1A: case 0xFF1B: case 0xFF3B: case 0xFF5B: { return AsianCompressionFlags::PunctuationLeft; } @@ -156,7 +147,7 @@ static void lcl_DrawRedLines( OutputDevice& rOutDev, const Point& rPoint, size_t nIndex, size_t nMaxEnd, - const tools::Long* pDXArray, + std::span<const sal_Int32> pDXArray, WrongList const * pWrongs, Degree10 nOrientation, const Point& rOrigin, @@ -220,8 +211,8 @@ static void lcl_DrawRedLines( OutputDevice& rOutDev, if (nOrientation) { - aPoint1 = Rotate(aPoint1, nOrientation, rOrigin); - aPoint2 = Rotate(aPoint2, nOrientation, rOrigin); + rOrigin.RotateAround(aPoint1, nOrientation); + rOrigin.RotateAround(aPoint2, nOrientation); } { @@ -237,35 +228,83 @@ static void lcl_DrawRedLines( OutputDevice& rOutDev, } } -static Point lcl_ImplCalcRotatedPos( Point rPos, Point rOrigin, double nSin, double nCos ) +// For Kashidas from sw/source/core/text/porlay.cxx + +#define IS_JOINING_GROUP(c, g) ( u_getIntPropertyValue( (c), UCHAR_JOINING_GROUP ) == U_JG_##g ) +#define isAinChar(c) IS_JOINING_GROUP((c), AIN) +#define isAlefChar(c) IS_JOINING_GROUP((c), ALEF) +#define isDalChar(c) IS_JOINING_GROUP((c), DAL) +#define isFehChar(c) (IS_JOINING_GROUP((c), FEH) || IS_JOINING_GROUP((c), AFRICAN_FEH)) +#define isGafChar(c) IS_JOINING_GROUP((c), GAF) +#define isHehChar(c) IS_JOINING_GROUP((c), HEH) +#define isKafChar(c) IS_JOINING_GROUP((c), KAF) +#define isLamChar(c) IS_JOINING_GROUP((c), LAM) +#define isQafChar(c) (IS_JOINING_GROUP((c), QAF) || IS_JOINING_GROUP((c), AFRICAN_QAF)) +#define isRehChar(c) IS_JOINING_GROUP((c), REH) +#define isTahChar(c) IS_JOINING_GROUP((c), TAH) +#define isTehMarbutaChar(c) IS_JOINING_GROUP((c), TEH_MARBUTA) +#define isWawChar(c) IS_JOINING_GROUP((c), WAW) +#define isSeenOrSadChar(c) (IS_JOINING_GROUP((c), SAD) || IS_JOINING_GROUP((c), SEEN)) + +// Beh and characters that behave like Beh in medial form. +static bool isBehChar(sal_Unicode cCh) { - Point aRotatedPos; - // Translation... - Point aTranslatedPos( rPos); - aTranslatedPos -= rOrigin; - - aRotatedPos.setX( static_cast<tools::Long>( nCos*aTranslatedPos.X() + nSin*aTranslatedPos.Y() ) ); - aRotatedPos.setY( static_cast<tools::Long>(- ( nSin*aTranslatedPos.X() - nCos*aTranslatedPos.Y() )) ); - aTranslatedPos = aRotatedPos; - // Translation... - aTranslatedPos += rOrigin; - - return aTranslatedPos; + bool bRet = false; + switch (u_getIntPropertyValue(cCh, UCHAR_JOINING_GROUP)) + { + case U_JG_BEH: + case U_JG_NOON: + case U_JG_AFRICAN_NOON: + case U_JG_NYA: + case U_JG_YEH: + case U_JG_FARSI_YEH: + case U_JG_BURUSHASKI_YEH_BARREE: + bRet = true; + break; + default: + bRet = false; + break; + } + + return bRet; } -static bool lcl_IsLigature( sal_Unicode cCh, sal_Unicode cNextCh ) // For Kashidas from sw/source/core/text/porlay.txt +// Yeh and characters that behave like Yeh in final form. +static bool isYehChar(sal_Unicode cCh) +{ + bool bRet = false; + switch (u_getIntPropertyValue(cCh, UCHAR_JOINING_GROUP)) + { + case U_JG_YEH: + case U_JG_FARSI_YEH: + case U_JG_YEH_BARREE: + case U_JG_BURUSHASKI_YEH_BARREE: + case U_JG_YEH_WITH_TAIL: + bRet = true; + break; + default: + bRet = false; + break; + } + + return bRet; +} + +static bool isTransparentChar ( sal_Unicode cCh ) +{ + return u_getIntPropertyValue( cCh, UCHAR_JOINING_TYPE ) == U_JT_TRANSPARENT; +} + +static bool lcl_IsLigature( sal_Unicode cCh, sal_Unicode cNextCh ) { // Lam + Alef - return ( 0x644 == cCh && 0x627 == cNextCh ) || - // Beh + Reh - ( 0x628 == cCh && 0x631 == cNextCh ); + return ( isLamChar ( cCh ) && isAlefChar ( cNextCh )); } -static bool lcl_ConnectToPrev( sal_Unicode cCh, sal_Unicode cPrevCh ) // For Kashidas from sw/source/core/text/porlay.txt +static bool lcl_ConnectToPrev( sal_Unicode cCh, sal_Unicode cPrevCh ) { - // Alef, Dal, Thal, Reh, Zain, and Waw do not connect to the left - bool bRet = 0x627 != cPrevCh && 0x62F != cPrevCh && 0x630 != cPrevCh && - 0x631 != cPrevCh && 0x632 != cPrevCh && 0x648 != cPrevCh; + const int32_t nJoiningType = u_getIntPropertyValue( cPrevCh, UCHAR_JOINING_TYPE ); + bool bRet = nJoiningType != U_JT_RIGHT_JOINING && nJoiningType != U_JT_NON_JOINING; // check for ligatures cPrevChar + cChar if ( bRet ) @@ -278,23 +317,23 @@ static bool lcl_ConnectToPrev( sal_Unicode cCh, sal_Unicode cPrevCh ) // For Ka void ImpEditEngine::UpdateViews( EditView* pCurView ) { - if ( !IsUpdateLayout() || IsFormatting() || aInvalidRect.IsEmpty() ) + if ( !IsUpdateLayout() || IsFormatting() || maInvalidRect.IsEmpty() ) return; DBG_ASSERT( IsFormatted(), "UpdateViews: Doc not formatted!" ); - for (EditView* pView : aEditViews) + for (EditView* pView : maEditViews) { pView->HideCursor(); - tools::Rectangle aClipRect( aInvalidRect ); + tools::Rectangle aClipRect(maInvalidRect); tools::Rectangle aVisArea( pView->GetVisArea() ); aClipRect.Intersection( aVisArea ); if ( !aClipRect.IsEmpty() ) { // convert to window coordinates... - aClipRect = pView->pImpEditView->GetWindowPos( aClipRect ); + aClipRect = pView->getImpl().GetWindowPos( aClipRect ); // moved to one executing method to allow finer control pView->InvalidateWindow(aClipRect); @@ -305,11 +344,11 @@ void ImpEditEngine::UpdateViews( EditView* pCurView ) if ( pCurView ) { - bool bGotoCursor = pCurView->pImpEditView->DoAutoScroll(); + bool bGotoCursor = pCurView->getImpl().DoAutoScroll(); pCurView->ShowCursor( bGotoCursor ); } - aInvalidRect = tools::Rectangle(); + maInvalidRect = tools::Rectangle(); CallStatusHdl(); } @@ -318,18 +357,18 @@ IMPL_LINK_NOARG(ImpEditEngine, OnlineSpellHdl, Timer *, void) if ( !Application::AnyInput( VclInputFlags::KEYBOARD ) && IsUpdateLayout() && IsFormatted() ) DoOnlineSpelling(); else - aOnlineSpellTimer.Start(); + maOnlineSpellTimer.Start(); } IMPL_LINK_NOARG(ImpEditEngine, IdleFormatHdl, Timer *, void) { - aIdleFormatter.ResetRestarts(); + maIdleFormatter.ResetRestarts(); // #i97146# check if that view is still available // else probably the idle format timer fired while we're already // downing - EditView* pView = aIdleFormatter.GetView(); - for (EditView* aEditView : aEditViews) + EditView* pView = maIdleFormatter.GetView(); + for (EditView* aEditView : maEditViews) { if( aEditView == pView ) { @@ -341,7 +380,7 @@ IMPL_LINK_NOARG(ImpEditEngine, IdleFormatHdl, Timer *, void) void ImpEditEngine::CheckIdleFormatter() { - aIdleFormatter.ForceTimeout(); + maIdleFormatter.ForceTimeout(); // If not idle, but still not formatted: if ( !IsFormatted() ) FormatDoc(); @@ -355,186 +394,218 @@ bool ImpEditEngine::IsPageOverflow( ) const void ImpEditEngine::FormatFullDoc() { - for ( sal_Int32 nPortion = 0; nPortion < GetParaPortions().Count(); nPortion++ ) - GetParaPortions()[nPortion].MarkSelectionInvalid( 0 ); + GetParaPortions().MarkAllSelectionsInvalid(0); FormatDoc(); } -void ImpEditEngine::FormatDoc() +tools::Long ImpEditEngine::FormatParagraphs(o3tl::sorted_vector<sal_Int32>& aRepaintParagraphList) { - if (!IsUpdateLayout() || IsFormatting()) - return; - - bIsFormatting = true; - - // Then I can also start the spell-timer... - if ( GetStatus().DoOnlineSpelling() ) - StartOnlineSpellTimer(); - + sal_Int32 nParaCount = GetParaPortions().Count(); tools::Long nY = 0; bool bGrow = false; - // Here already, so that not always in CreateLines... - bool bMapChanged = ImpCheckRefMapMode(); - std::set<sal_Int32> aRepaintParas; - - for ( sal_Int32 nPara = 0; nPara < GetParaPortions().Count(); nPara++ ) + for (sal_Int32 nParagraph = 0; nParagraph < nParaCount; nParagraph++) { - ParaPortion& rParaPortion = GetParaPortions()[nPara]; - if ( rParaPortion.MustRepaint() || ( rParaPortion.IsInvalid() && rParaPortion.IsVisible() ) ) + ParaPortion& rParaPortion = GetParaPortions().getRef(nParagraph); + if (rParaPortion.MustRepaint() || (rParaPortion.IsInvalid() && rParaPortion.IsVisible())) { // No formatting should be necessary for MustRepaint()! - if ( !rParaPortion.IsInvalid() || CreateLines( nPara, nY ) ) + if (CreateLines(nParagraph, nY)) { - if ( !bGrow && GetTextRanger() ) + if (!bGrow && GetTextRanger()) { // For a change in height all below must be reformatted... - for ( sal_Int32 n = nPara+1; n < GetParaPortions().Count(); n++ ) + for (sal_Int32 n = nParagraph + 1; n < nParaCount; n++) { - ParaPortion& rPP = GetParaPortions()[n]; - rPP.MarkSelectionInvalid( 0 ); - rPP.GetLines().Reset(); + ParaPortion& rParaPortionToInvalidate = GetParaPortions().getRef(n); + rParaPortionToInvalidate.MarkSelectionInvalid(0); + rParaPortionToInvalidate.GetLines().Reset(); } } bGrow = true; - if ( IsCallParaInsertedOrDeleted() ) + if (IsCallParaInsertedOrDeleted()) { - GetEditEnginePtr()->ParagraphHeightChanged( nPara ); + GetEditEnginePtr()->ParagraphHeightChanged(nParagraph); - for (EditView* pView : aEditViews) + for (EditView* pView : maEditViews) { - ImpEditView* pImpView = pView->pImpEditView.get(); - pImpView->ScrollStateChange(); + pView->getImpl().ScrollStateChange(); } } - rParaPortion.SetMustRepaint( false ); + rParaPortion.SetMustRepaint(false); } - aRepaintParas.insert(nPara); + aRepaintParagraphList.insert(nParagraph); } nY += rParaPortion.GetHeight(); } + return nY; +} + +namespace +{ +constexpr std::array<ScalingParameters, 13> constScaleLevels = +{ + ScalingParameters{ 1.000, 1.000, 1.0, 0.9 }, + ScalingParameters{ 0.925, 0.925, 1.0, 0.9 }, + ScalingParameters{ 0.925, 0.925, 1.0, 0.8 }, + ScalingParameters{ 0.850, 0.850, 1.0, 0.9 }, + ScalingParameters{ 0.850, 0.850, 1.0, 0.8 }, + ScalingParameters{ 0.775, 0.775, 1.0, 0.8 }, + ScalingParameters{ 0.700, 0.700, 1.0, 0.8 }, + ScalingParameters{ 0.625, 0.625, 1.0, 0.8 }, + ScalingParameters{ 0.550, 0.550, 1.0, 0.8 }, + ScalingParameters{ 0.475, 0.475, 1.0, 0.8 }, + ScalingParameters{ 0.400, 0.400, 1.0, 0.8 }, + ScalingParameters{ 0.325, 0.325, 1.0, 0.8 }, + ScalingParameters{ 0.250, 0.250, 1.0, 0.8 }, +}; + +} // end anonymous ns + +void ImpEditEngine::ScaleContentToFitWindow(o3tl::sorted_vector<sal_Int32>& aRepaintParagraphList) +{ + if (!maCustomScalingParameters.areValuesDefault()) + maScalingParameters = maCustomScalingParameters; + + tools::Long nHeight = FormatParagraphs(aRepaintParagraphList); + bool bOverflow = nHeight > (maMaxAutoPaperSize.Height() * mnColumns); + + size_t nCurrentScaleLevel = 0; + while (bOverflow && nCurrentScaleLevel < constScaleLevels.size()) + { + // Clean-up and reset paragraphs + aRepaintParagraphList.clear(); + for (auto& pParaPortionToInvalidate : GetParaPortions()) + { + pParaPortionToInvalidate->GetLines().Reset(); + pParaPortionToInvalidate->MarkSelectionInvalid(0); + pParaPortionToInvalidate->SetMustRepaint(true); + } + + // Get new scaling parameters + maScalingParameters = constScaleLevels[nCurrentScaleLevel]; + + // Try again with different scaling factor + nHeight = FormatParagraphs(aRepaintParagraphList); + bOverflow = nHeight > (maMaxAutoPaperSize.Height() * mnColumns); + + // Increase scale level + nCurrentScaleLevel++; + } +} - aInvalidRect = tools::Rectangle(); // make empty +void ImpEditEngine::FormatDoc() +{ + if (!IsUpdateLayout() || IsFormatting()) + return; + + mbIsFormatting = true; + + // Then I can also start the spell-timer... + if (GetStatus().DoOnlineSpelling()) + StartOnlineSpellTimer(); + + // Reserve, as it should match the current number of paragraphs + o3tl::sorted_vector<sal_Int32> aRepaintParagraphList; + aRepaintParagraphList.reserve(GetParaPortions().Count()); + + if (maStatus.DoStretch()) + ScaleContentToFitWindow(aRepaintParagraphList); + else + FormatParagraphs(aRepaintParagraphList); + + maInvalidRect = tools::Rectangle(); // make empty // One can also get into the formatting through UpdateMode ON=>OFF=>ON... // enable optimization first after Vobis delivery... { tools::Long nNewHeightNTP; tools::Long nNewHeight = CalcTextHeight(&nNewHeightNTP); - tools::Long nDiff = nNewHeight - nCurTextHeight; + tools::Long nDiff = nNewHeight - mnCurTextHeight; if ( nDiff ) - aStatus.GetStatusWord() |= !IsEffectivelyVertical() ? EditStatusFlags::TextHeightChanged : EditStatusFlags::TEXTWIDTHCHANGED; + { + maInvalidRect.Union(tools::Rectangle::Normalize( + { 0, nNewHeight }, { getWidthDirectionAware(maPaperSize), mnCurTextHeight })); + maStatus.GetStatusWord() |= !IsEffectivelyVertical() ? EditStatusFlags::TextHeightChanged : EditStatusFlags::TEXTWIDTHCHANGED; + } - nCurTextHeight = nNewHeight; - nCurTextHeightNTP = nNewHeightNTP; + mnCurTextHeight = nNewHeight; + mnCurTextHeightNTP = nNewHeightNTP; - if ( aStatus.AutoPageSize() ) + if ( maStatus.AutoPageSize() ) CheckAutoPageSize(); else if ( nDiff ) { - for (EditView* pView : aEditViews) + for (EditView* pView : maEditViews) { - ImpEditView* pImpView = pView->pImpEditView.get(); - if ( pImpView->DoAutoHeight() ) + ImpEditView& rImpView = pView->getImpl(); + if (rImpView.DoAutoHeight()) { - Size aSz( pImpView->GetOutputArea().GetWidth(), nCurTextHeight ); - if ( aSz.Height() > aMaxAutoPaperSize.Height() ) - aSz.setHeight( aMaxAutoPaperSize.Height() ); - else if ( aSz.Height() < aMinAutoPaperSize.Height() ) - aSz.setHeight( aMinAutoPaperSize.Height() ); - pImpView->ResetOutputArea( tools::Rectangle( - pImpView->GetOutputArea().TopLeft(), aSz ) ); + Size aSz(rImpView.GetOutputArea().GetWidth(), mnCurTextHeight); + if ( aSz.Height() > maMaxAutoPaperSize.Height() ) + aSz.setHeight( maMaxAutoPaperSize.Height() ); + else if ( aSz.Height() < maMinAutoPaperSize.Height() ) + aSz.setHeight( maMinAutoPaperSize.Height() ); + rImpView.ResetOutputArea( tools::Rectangle(rImpView.GetOutputArea().TopLeft(), aSz)); } } } - if (nDiff) - aInvalidRect.Union(tools::Rectangle::Justify( - { 0, nNewHeight }, { getWidthDirectionAware(aPaperSize), nCurTextHeight })); - - if (!aRepaintParas.empty()) + if (!aRepaintParagraphList.empty()) { auto CombineRepaintParasAreas = [&](const LineAreaInfo& rInfo) { - if (aRepaintParas.count(rInfo.nPortion)) - aInvalidRect.Union(rInfo.aArea); + if (aRepaintParagraphList.count(rInfo.nPortion)) + maInvalidRect.Union(rInfo.aArea); return CallbackResult::Continue; }; IterateLineAreas(CombineRepaintParasAreas, IterFlag::inclILS); } } - bIsFormatting = false; - bFormatted = true; - - if ( bMapChanged ) - GetRefDevice()->Pop(); + mbIsFormatting = false; + mbFormatted = true; CallStatusHdl(); // If Modified... } -bool ImpEditEngine::ImpCheckRefMapMode() -{ - bool bChange = false; - - if ( aStatus.DoFormat100() ) - { - MapMode aMapMode( GetRefDevice()->GetMapMode() ); - if ( aMapMode.GetScaleX().GetNumerator() != aMapMode.GetScaleX().GetDenominator() ) - bChange = true; - else if ( aMapMode.GetScaleY().GetNumerator() != aMapMode.GetScaleY().GetDenominator() ) - bChange = true; - - if ( bChange ) - { - Fraction Scale1( 1, 1 ); - aMapMode.SetScaleX( Scale1 ); - aMapMode.SetScaleY( Scale1 ); - GetRefDevice()->Push(); - GetRefDevice()->SetMapMode( aMapMode ); - } - } - - return bChange; -} - void ImpEditEngine::CheckAutoPageSize() { Size aPrevPaperSize( GetPaperSize() ); if ( GetStatus().AutoPageWidth() ) - aPaperSize.setWidth( !IsEffectivelyVertical() ? CalcTextWidth( true ) : GetTextHeight() ); + maPaperSize.setWidth( !IsEffectivelyVertical() ? CalcTextWidth( true ) : GetTextHeight() ); if ( GetStatus().AutoPageHeight() ) - aPaperSize.setHeight( !IsEffectivelyVertical() ? GetTextHeight() : CalcTextWidth( true ) ); + maPaperSize.setHeight( !IsEffectivelyVertical() ? GetTextHeight() : CalcTextWidth( true ) ); - SetValidPaperSize( aPaperSize ); // consider Min, Max + SetValidPaperSize( maPaperSize ); // consider Min, Max - if ( aPaperSize == aPrevPaperSize ) + if ( maPaperSize == aPrevPaperSize ) return; - if ( ( !IsEffectivelyVertical() && ( aPaperSize.Width() != aPrevPaperSize.Width() ) ) - || ( IsEffectivelyVertical() && ( aPaperSize.Height() != aPrevPaperSize.Height() ) ) ) + if ( ( !IsEffectivelyVertical() && ( maPaperSize.Width() != aPrevPaperSize.Width() ) ) + || ( IsEffectivelyVertical() && ( maPaperSize.Height() != aPrevPaperSize.Height() ) ) ) { // If ahead is centered / right or tabs... - aStatus.GetStatusWord() |= !IsEffectivelyVertical() ? EditStatusFlags::TEXTWIDTHCHANGED : EditStatusFlags::TextHeightChanged; + maStatus.GetStatusWord() |= !IsEffectivelyVertical() ? EditStatusFlags::TEXTWIDTHCHANGED : EditStatusFlags::TextHeightChanged; for ( sal_Int32 nPara = 0; nPara < GetParaPortions().Count(); nPara++ ) { // Only paragraphs which are not aligned to the left need to be // reformatted, the height can not be changed here anymore. - ParaPortion& rParaPortion = GetParaPortions()[nPara]; + ParaPortion& rParaPortion = GetParaPortions().getRef(nPara); SvxAdjust eJustification = GetJustification( nPara ); if ( eJustification != SvxAdjust::Left ) { - rParaPortion.MarkSelectionInvalid( 0 ); + rParaPortion.MarkSelectionInvalid(0); CreateLines( nPara, 0 ); // 0: For AutoPageSize no TextRange! } } } - Size aInvSize = aPaperSize; - if ( aPaperSize.Width() < aPrevPaperSize.Width() ) + Size aInvSize = maPaperSize; + if ( maPaperSize.Width() < aPrevPaperSize.Width() ) aInvSize.setWidth( aPrevPaperSize.Width() ); - if ( aPaperSize.Height() < aPrevPaperSize.Height() ) + if ( maPaperSize.Height() < aPrevPaperSize.Height() ) aInvSize.setHeight( aPrevPaperSize.Height() ); Size aSz( aInvSize ); @@ -543,18 +614,18 @@ void ImpEditEngine::CheckAutoPageSize() aSz.setWidth( aInvSize.Height() ); aSz.setHeight( aInvSize.Width() ); } - aInvalidRect = tools::Rectangle( Point(), aSz ); + maInvalidRect = tools::Rectangle( Point(), aSz ); - for (EditView* pView : aEditViews) + for (EditView* pView : maEditViews) { - pView->pImpEditView->RecalcOutputArea(); + pView->getImpl().RecalcOutputArea(); } } void ImpEditEngine::CheckPageOverflow() { - SAL_INFO("editeng.chaining", "[CONTROL_STATUS] AutoPageSize is " << (( aStatus.GetControlWord() & EEControlBits::AUTOPAGESIZE ) ? "ON" : "OFF") ); + SAL_INFO("editeng.chaining", "[CONTROL_STATUS] AutoPageSize is " << (( maStatus.GetControlWord() & EEControlBits::AUTOPAGESIZE ) ? "ON" : "OFF") ); tools::Long nBoxHeight = GetMaxAutoPaperSize().Height(); SAL_INFO("editeng.chaining", "[OVERFLOW-CHECK] Current MaxAutoPaperHeight is " << nBoxHeight); @@ -572,12 +643,12 @@ void ImpEditEngine::CheckPageOverflow() { // which paragraph is the first to cause higher size of the box? ImplUpdateOverflowingParaNum( nBoxHeight); // XXX: currently only for horizontal text - //aStatus.SetPageOverflow(true); + //maStatus.SetPageOverflow(true); mbNeedsChainingHandling = true; } else { // No overflow if within box boundaries - //aStatus.SetPageOverflow(false); + //maStatus.SetPageOverflow(false); mbNeedsChainingHandling = false; } @@ -585,7 +656,8 @@ void ImpEditEngine::CheckPageOverflow() static sal_Int32 ImplCalculateFontIndependentLineSpacing( const sal_Int32 nFontHeight ) { - return ( nFontHeight * 12 ) / 10; // + 20% + constexpr const double f120Percent = 12.0 / 10.0; + return basegfx::fround(nFontHeight * f120Percent); // + 20% } tools::Long ImpEditEngine::GetColumnWidth(const Size& rPaperSize) const @@ -595,46 +667,82 @@ tools::Long ImpEditEngine::GetColumnWidth(const Size& rPaperSize) const return (nWidth - mnColumnSpacing * (mnColumns - 1)) / mnColumns; } +bool ImpEditEngine::createLinesForEmptyParagraph(ParaPortion& rParaPortion) +{ + // fast special treatment... + if (rParaPortion.GetTextPortions().Count()) + rParaPortion.GetTextPortions().Reset(); + if (rParaPortion.GetLines().Count()) + rParaPortion.GetLines().Reset(); + + CreateAndInsertEmptyLine(rParaPortion); + return FinishCreateLines(rParaPortion); +} + +tools::Long ImpEditEngine::calculateMaxLineWidth(tools::Long nStartX, SvxLRSpaceItem const& rLRItem) +{ + const bool bAutoSize = IsEffectivelyVertical() ? maStatus.AutoPageHeight() : maStatus.AutoPageWidth(); + tools::Long nMaxLineWidth = GetColumnWidth(bAutoSize ? maMaxAutoPaperSize : maPaperSize); + + nMaxLineWidth -= scaleXSpacingValue(rLRItem.GetRight()); + nMaxLineWidth -= nStartX; + + // If PaperSize == long_max, one cannot take away any negative + // first line indent. (Overflow) + if (nMaxLineWidth < 0 && nStartX < 0) + nMaxLineWidth = GetColumnWidth(maPaperSize) - scaleXSpacingValue(rLRItem.GetRight()); + + // If still less than 0, it may be just the right edge. + if (nMaxLineWidth <= 0) + nMaxLineWidth = 1; + + return nMaxLineWidth; +} + bool ImpEditEngine::CreateLines( sal_Int32 nPara, sal_uInt32 nStartPosY ) { - ParaPortion& rParaPortion = GetParaPortions()[nPara]; + assert(GetParaPortions().exists(nPara) && "Portion paragraph index is not valid"); + ParaPortion& rParaPortion = GetParaPortions().getRef(nPara); // sal_Bool: Changes in the height of paragraph Yes / No - sal_True/sal_False - assert( rParaPortion.GetNode() && "Portion without Node in CreateLines" ); + assert(rParaPortion.GetNode() && "Portion without Node in CreateLines" ); DBG_ASSERT( rParaPortion.IsVisible(), "Invisible paragraphs not formatted!" ); DBG_ASSERT( rParaPortion.IsInvalid(), "CreateLines: Portion not invalid!" ); - bool bProcessingEmptyLine = ( rParaPortion.GetNode()->Len() == 0 ); - bool bEmptyNodeWithPolygon = ( rParaPortion.GetNode()->Len() == 0 ) && GetTextRanger(); + bool bProcessingEmptyLine = rParaPortion.GetNode()->Len() == 0 ; + bool bEmptyNodeWithPolygon = rParaPortion.GetNode()->Len() == 0 && GetTextRanger(); // Fast special treatment for empty paragraphs... - - if ( ( rParaPortion.GetNode()->Len() == 0 ) && !GetTextRanger() ) + bool bEmptyParagraph = rParaPortion.GetNode()->Len() == 0 && !GetTextRanger(); + if (bEmptyParagraph) + return createLinesForEmptyParagraph(rParaPortion); + + sal_Int64 nCurrentPosY = nStartPosY; + // If we're allowed to skip parts outside and this cannot possibly fit in the given height, + // bail out to avoid possibly formatting a lot of text that will not be used. For the first + // paragraph still format at least a bit. + if( mbSkipOutsideFormat && nPara != 0 + && !maStatus.AutoPageHeight() && maPaperSize.Height() < nCurrentPosY ) { - // fast special treatment... - if ( rParaPortion.GetTextPortions().Count() ) - rParaPortion.GetTextPortions().Reset(); - if ( rParaPortion.GetLines().Count() ) - rParaPortion.GetLines().Reset(); - CreateAndInsertEmptyLine( &rParaPortion ); - return FinishCreateLines( &rParaPortion ); + return false; } + //If the paragraph SvxFrameDirection is Stacked, use STACKED + const SvxFrameDirectionItem* pFrameDirItem = &GetParaAttrib(nPara, EE_PARA_WRITINGDIR); + bool bStacked = pFrameDirItem->GetValue() == SvxFrameDirection::Stacked; + if (bStacked) + maStatus.TurnOnFlags(EEControlBits::STACKED); + else + maStatus.TurnOffFlags(EEControlBits::STACKED); // Initialization... - - // Always format for 100%: - bool bMapChanged = ImpCheckRefMapMode(); - - if ( rParaPortion.GetLines().Count() == 0 ) + if (rParaPortion.GetLines().Count() == 0) { - EditLine* pL = new EditLine; - rParaPortion.GetLines().Append(pL); + rParaPortion.GetLines().Append(std::make_unique<EditLine>()); } - // Get Paragraph attributes... ContentNode* const pNode = rParaPortion.GetNode(); @@ -655,27 +763,28 @@ bool ImpEditEngine::CreateLines( sal_Int32 nPara, sal_uInt32 nStartPosY ) const sal_Int32 nInvalidEnd = nInvalidStart + std::abs( nInvalidDiff ); bool bQuickFormat = false; - if ( !bEmptyNodeWithPolygon && !HasScriptType( nPara, i18n::ScriptType::COMPLEX ) ) + + // Determine if quick format should be used + if (!bEmptyNodeWithPolygon && !HasScriptType(nPara, i18n::ScriptType::COMPLEX)) { - if ( ( rParaPortion.IsSimpleInvalid() ) && ( nInvalidDiff > 0 ) && - ( pNode->GetString().indexOf( CH_FEATURE, nInvalidStart ) > nInvalidEnd ) ) + if (rParaPortion.IsSimpleInvalid() && + rParaPortion.GetInvalidDiff() > 0 && + pNode->GetString().indexOf(CH_FEATURE, nInvalidStart) > nInvalidEnd) { bQuickFormat = true; } - else if ( ( rParaPortion.IsSimpleInvalid() ) && ( nInvalidDiff < 0 ) ) + else if (rParaPortion.IsSimpleInvalid() && nInvalidDiff < 0) { // check if delete over the portion boundaries was done... sal_Int32 nStart = nInvalidStart; // DOUBLE !!!!!!!!!!!!!!! sal_Int32 nEnd = nStart - nInvalidDiff; // negative bQuickFormat = true; sal_Int32 nPos = 0; - sal_Int32 nPortions = rParaPortion.GetTextPortions().Count(); - for ( sal_Int32 nTP = 0; nTP < nPortions; nTP++ ) + for (auto const& pTextPortion : rParaPortion.GetTextPortions()) { // There must be no start / end in the deleted area. - const TextPortion& rTP = rParaPortion.GetTextPortions()[ nTP ]; - nPos = nPos + rTP.GetLen(); - if ( ( nPos > nStart ) && ( nPos < nEnd ) ) + nPos = nPos + pTextPortion->GetLen(); + if (nPos > nStart && nPos < nEnd) { bQuickFormat = false; break; @@ -691,7 +800,7 @@ bool ImpEditEngine::CreateLines( sal_Int32 nPara, sal_uInt32 nStartPosY ) sal_Int32 nRealInvalidStart = nInvalidStart; - if ( bEmptyNodeWithPolygon ) + if (bEmptyNodeWithPolygon) { TextPortion* pDummyPortion = new TextPortion( 0 ); rParaPortion.GetTextPortions().Reset(); @@ -700,14 +809,13 @@ bool ImpEditEngine::CreateLines( sal_Int32 nPara, sal_uInt32 nStartPosY ) else if ( bQuickFormat ) { // faster Method: - RecalcTextPortion( &rParaPortion, nInvalidStart, nInvalidDiff ); + RecalcTextPortion(rParaPortion, nInvalidStart, nInvalidDiff); } else // nRealInvalidStart can be before InvalidStart, since Portions were deleted... { - CreateTextPortions( &rParaPortion, nRealInvalidStart ); + CreateTextPortions(rParaPortion, nRealInvalidStart); } - // Search for line with InvalidPos, start one line before // Flag the line => do not remove it ! @@ -725,35 +833,38 @@ bool ImpEditEngine::CreateLines( sal_Int32 nPara, sal_uInt32 nStartPosY ) } // Begin one line before... // If it is typed at the end, the line in front cannot change. - if ( nLine && ( !rParaPortion.IsSimpleInvalid() || ( nInvalidEnd < pNode->Len() ) || ( nInvalidDiff <= 0 ) ) ) + if (nLine && (!rParaPortion.IsSimpleInvalid() || + (nInvalidEnd < pNode->Len()) || + (nInvalidDiff <= 0))) + { nLine--; + } - EditLine* pLine = &rParaPortion.GetLines()[nLine]; + tools::Rectangle aBulletArea{Point(), Point()}; - static const tools::Rectangle aZeroArea { Point(), Point() }; - tools::Rectangle aBulletArea( aZeroArea ); - if ( !nLine ) + if (!nLine) { - aBulletArea = GetEditEnginePtr()->GetBulletArea( GetParaPortions().GetPos( &rParaPortion ) ); + aBulletArea = GetEditEnginePtr()->GetBulletArea(GetParaPortions().GetPos(&rParaPortion)); if ( !aBulletArea.IsWidthEmpty() && aBulletArea.Right() > 0 ) - rParaPortion.SetBulletX( static_cast<sal_Int32>(GetXValue( aBulletArea.Right() )) ); + rParaPortion.SetBulletX(sal_Int32(scaleXSpacingValue(aBulletArea.Right()))); else rParaPortion.SetBulletX( 0 ); // if Bullet is set incorrectly } - // Reformat all lines from here... + int nStartNextLineAfterMultiLineField = 0; + sal_Int32 nDelFromLine = -1; bool bLineBreak = false; + EditLine* pLine = &rParaPortion.GetLines()[nLine]; sal_Int32 nIndex = pLine->GetStart(); EditLine aSaveLine( *pLine ); - SvxFont aTmpFont( pNode->GetCharAttribs().GetDefFont() ); - ImplInitLayoutMode(*GetRefDevice(), nPara, nIndex); + SvxFont aTmpFont( pNode->GetCharAttribs().GetDefFont() ); - std::vector<tools::Long> aBuf( pNode->Len() ); + KernArray aCharPositionArray; bool bSameLineAgain = false; // For TextRanger, if the height changes. TabInfo aCurrentTab; @@ -763,39 +874,33 @@ bool ImpEditEngine::CreateLines( sal_Int32 nPara, sal_uInt32 nStartPosY ) while ( ( nIndex < pNode->Len() ) || bForceOneRun ) { + assert(pLine); + bForceOneRun = false; + bool bFieldStartNextLine = false; bool bEOL = false; bool bEOC = false; sal_Int32 nPortionStart = 0; sal_Int32 nPortionEnd = 0; - tools::Long nStartX = GetXValue( rLRItem.GetTextLeft() + nSpaceBeforeAndMinLabelWidth ); + tools::Long nStartX = scaleXSpacingValue(rLRItem.GetTextLeft() + nSpaceBeforeAndMinLabelWidth); + // Multiline hyperlink may need to know if the next line is bigger. + tools::Long nStartXNextLine = nStartX; if ( nIndex == 0 ) { - tools::Long nFI = GetXValue( rLRItem.GetTextFirstLineOffset() ); + tools::Long nFI = scaleXSpacingValue(rLRItem.GetTextFirstLineOffset()); nStartX += nFI; - if ( !nLine && ( rParaPortion.GetBulletX() > nStartX ) ) + if (!nLine && rParaPortion.GetBulletX() > nStartX) { - nStartX = rParaPortion.GetBulletX(); + nStartX = rParaPortion.GetBulletX(); } } - const bool bAutoSize = IsEffectivelyVertical() ? aStatus.AutoPageHeight() : aStatus.AutoPageWidth(); - tools::Long nMaxLineWidth = GetColumnWidth(bAutoSize ? aMaxAutoPaperSize : aPaperSize); - - nMaxLineWidth -= GetXValue( rLRItem.GetRight() ); - nMaxLineWidth -= nStartX; - - // If PaperSize == long_max, one cannot take away any negative - // first line indent. (Overflow) - if ( ( nMaxLineWidth < 0 ) && ( nStartX < 0 ) ) - nMaxLineWidth = GetColumnWidth(aPaperSize) - GetXValue(rLRItem.GetRight()); - - // If still less than 0, it may be just the right edge. - if ( nMaxLineWidth <= 0 ) - nMaxLineWidth = 1; + nStartX += nStartNextLineAfterMultiLineField; + nStartNextLineAfterMultiLineField = 0; + tools::Long nMaxLineWidth = calculateMaxLineWidth(nStartX, rLRItem); // Problem: // Since formatting starts a line _before_ the invalid position, @@ -818,7 +923,7 @@ bool ImpEditEngine::CreateLines( sal_Int32 nPara, sal_uInt32 nStartPosY ) { GetTextRanger()->SetVertical( IsEffectivelyVertical() ); - tools::Long nTextY = nStartPosY + GetEditCursor( &rParaPortion, pLine, pLine->GetStart(), GetCursorFlags::NONE ).Top(); + tools::Long nTextY = nStartPosY + GetEditCursor(rParaPortion, *pLine, pLine->GetStart(), CursorFlags()).Top(); if ( !bSameLineAgain ) { SeekCursor( pNode, nTmpPos+1, aTmpFont ); @@ -871,7 +976,7 @@ bool ImpEditEngine::CreateLines( sal_Int32 nPara, sal_uInt32 nStartPosY ) } nXWidth = nMaxRangeWidth; if ( nXWidth ) - nMaxLineWidth = nXWidth - nStartX - GetXValue( rLRItem.GetRight() ); + nMaxLineWidth = nXWidth - nStartX - scaleXSpacingValue(rLRItem.GetRight()); else { // Try further down in the polygon. @@ -929,10 +1034,10 @@ bool ImpEditEngine::CreateLines( sal_Int32 nPara, sal_uInt32 nStartPosY ) nTmpWidth -= rPrev.GetSize().Width(); nTmpPos = nTmpPos - rPrev.GetLen(); rPrev.SetLen(rPrev.GetLen() + nTmpLen); - rPrev.GetSize().setWidth( -1 ); + rPrev.setWidth(-1); } - assert( nTmpPortion < rParaPortion.GetTextPortions().Count() && "No more Portions left!" ); + assert(nTmpPortion < rParaPortion.GetTextPortions().Count() && "No more Portions left!"); pPortion = &rParaPortion.GetTextPortions()[nTmpPortion]; } @@ -961,14 +1066,15 @@ bool ImpEditEngine::CreateLines( sal_Int32 nPara, sal_uInt32 nStartPosY ) tools::Long nOldTmpWidth = nTmpWidth; // Search for Tab-Pos... - tools::Long nCurPos = nTmpWidth+nStartX; + tools::Long nCurPos = nTmpWidth + nStartX; // consider scaling - if ( aStatus.DoStretch() && ( nStretchX != 100 ) ) - nCurPos = nCurPos*100/std::max(static_cast<sal_Int32>(nStretchX), static_cast<sal_Int32>(1)); + double fFontScalingX = maScalingParameters.fFontX; + if (maStatus.DoStretch() && (fFontScalingX != 1.0)) + nCurPos = basegfx::fround<tools::Long>(double(nCurPos) / std::max(fFontScalingX, 0.01)); - short nAllSpaceBeforeText = static_cast< short >(rLRItem.GetTextLeft()/* + rLRItem.GetTextLeft()*/ + nSpaceBeforeAndMinLabelWidth); - aCurrentTab.aTabStop = pNode->GetContentAttribs().FindTabStop( nCurPos - nAllSpaceBeforeText /*rLRItem.GetTextLeft()*/, aEditDoc.GetDefTab() ); - aCurrentTab.nTabPos = GetXValue( static_cast<tools::Long>( aCurrentTab.aTabStop.GetTabPos() + nAllSpaceBeforeText /*rLRItem.GetTextLeft()*/ ) ); + short nAllSpaceBeforeText = short(rLRItem.GetTextLeft()); + aCurrentTab.aTabStop = pNode->GetContentAttribs().FindTabStop( nCurPos - nAllSpaceBeforeText , maEditDoc.GetDefTab() ); + aCurrentTab.nTabPos = tools::Long(aCurrentTab.aTabStop.GetTabPos() + nAllSpaceBeforeText); aCurrentTab.bValid = false; // Switch direction in R2L para... @@ -992,23 +1098,23 @@ bool ImpEditEngine::CreateLines( sal_Int32 nPara, sal_uInt32 nStartPosY ) pPortion->SetKind(PortionKind::TAB); pPortion->SetExtraValue( aCurrentTab.aTabStop.GetFill() ); - pPortion->GetSize().setWidth( aCurrentTab.nTabPos - (nTmpWidth+nStartX) ); + pPortion->setWidth( aCurrentTab.nTabPos - (nTmpWidth+nStartX) ); // Height needed... SeekCursor( pNode, nTmpPos+1, aTmpFont ); - pPortion->GetSize().setHeight( aTmpFont.QuickGetTextSize( GetRefDevice(), OUString(), 0, 0 ).Height() ); + pPortion->setHeight( GetRefDevice()->GetTextHeight() ); DBG_ASSERT( pPortion->GetSize().Width() >= 0, "Tab incorrectly calculated!" ); nTmpWidth = aCurrentTab.nTabPos-nStartX; // If this is the first token on the line, - // and nTmpWidth > aPaperSize.Width, => infinite loop! + // and nTmpWidth > maPaperSize.Width, => infinite loop! if ( ( nTmpWidth >= nXWidth ) && ( nTmpPortion == pLine->GetStartPortion() ) ) { // What now? // make the tab fitting - pPortion->GetSize().setWidth( nXWidth-nOldTmpWidth ); + pPortion->setWidth( nXWidth-nOldTmpWidth ); nTmpWidth = nXWidth-1; bEOL = true; bBrokenLine = true; @@ -1022,7 +1128,7 @@ bool ImpEditEngine::CreateLines( sal_Int32 nPara, sal_uInt32 nStartPosY ) case EE_FEATURE_LINEBR: { assert( pPortion ); - pPortion->GetSize().setWidth( 0 ); + pPortion->setWidth(0); bEOL = true; bLineBreak = true; pPortion->SetKind( PortionKind::LINEBREAK ); @@ -1040,11 +1146,12 @@ bool ImpEditEngine::CreateLines( sal_Int32 nPara, sal_uInt32 nStartPosY ) OUString aFieldValue = static_cast<const EditCharAttribField*>(pNextFeature)->GetFieldValue(); // get size, but also DXArray to allow length information in line breaking below - std::vector<tools::Long> aTmpDXArray; - pPortion->GetSize() = aTmpFont.QuickGetTextSize(GetRefDevice(), aFieldValue, 0, aFieldValue.getLength(), &aTmpDXArray); + KernArray aTmpDXArray; + pPortion->SetSize(aTmpFont.QuickGetTextSize(GetRefDevice(), + aFieldValue, 0, aFieldValue.getLength(), &aTmpDXArray)); // So no scrolling for oversized fields - if ( pPortion->GetSize().Width() > nXWidth ) + if (pPortion->GetSize().Width() > nXWidth - nTmpWidth) { // create ExtraPortionInfo on-demand, flush lineBreaksList ExtraPortionInfo *pExtraInfo = pPortion->GetExtraInfos(); @@ -1076,6 +1183,7 @@ bool ImpEditEngine::CreateLines( sal_Int32 nPara, sal_uInt32 nStartPosY ) nDone)); sal_Int32 nLastCellBreak(0); sal_Int32 nLineStartX(0); + nLineStartX = -nTmpWidth; // always add 1st line break (safe, we already know we are larger than nXWidth) pExtraInfo->lineBreaksList.push_back(0); @@ -1093,6 +1201,22 @@ bool ImpEditEngine::CreateLines( sal_Int32 nPara, sal_uInt32 nStartPosY ) if(0 != a) { pExtraInfo->lineBreaksList.push_back(a); + // the following lines may be different sized + if (nStartX > nStartXNextLine) + { + nXWidth += nStartX - nStartXNextLine; + pLine->SetNextLinePosXDiff(nStartX + - nStartXNextLine); + nStartXNextLine = nStartX; + } + } + else + { + //even the 1. char does not fit.. + //this means the field should start on next line + //except if the actual line is a full line already + if (nLineStartX < 0 || nStartX > nStartXNextLine) + bFieldStartNextLine = true; } // moveLineStart forward in X @@ -1110,6 +1234,17 @@ bool ImpEditEngine::CreateLines( sal_Int32 nPara, sal_uInt32 nStartPosY ) nDone); } } + //next Line should start here... after this field end + if (!bFieldStartNextLine) + nStartNextLineAfterMultiLineField + = aTmpDXArray[nTextLength - 1] - nLineStartX; + else if (pExtraInfo) + { + // if the 1. character does not fit, + // but there is pExtraInfo, then delete it + pPortion->SetExtraInfos(nullptr); + pExtraInfo = nullptr; + } } nTmpWidth += pPortion->GetSize().Width(); EditLine::CharPosArrayType& rArray = pLine->GetCharPosArray(); @@ -1117,7 +1252,7 @@ bool ImpEditEngine::CreateLines( sal_Int32 nPara, sal_uInt32 nStartPosY ) rArray.insert(rArray.begin()+nPos, pPortion->GetSize().Width()); pPortion->SetKind(PortionKind::FIELD); // If this is the first token on the line, - // and nTmpWidth > aPaperSize.Width, => infinite loop! + // and nTmpWidth > maPaperSize.Width, => infinite loop! if ( ( nTmpWidth >= nXWidth ) && ( nTmpPortion == pLine->GetStartPortion() ) ) { nTmpWidth = nXWidth-1; @@ -1145,33 +1280,36 @@ bool ImpEditEngine::CreateLines( sal_Int32 nPara, sal_uInt32 nStartPosY ) if (bContinueLastPortion) { - Size aSize( aTmpFont.QuickGetTextSize( GetRefDevice(), - rParaPortion.GetNode()->GetString(), nTmpPos, nPortionLen, &aBuf )); - pPortion->GetSize().AdjustWidth(aSize.Width() ); + Size aSize = aTmpFont.QuickGetTextSize( GetRefDevice(), rParaPortion.GetNode()->GetString(), nTmpPos, nPortionLen, &aCharPositionArray, bStacked); + pPortion->adjustSize(aSize.Width(), 0); if (pPortion->GetSize().Height() < aSize.Height()) - pPortion->GetSize().setHeight( aSize.Height() ); + pPortion->setHeight(aSize.Height()); } else { - pPortion->GetSize() = aTmpFont.QuickGetTextSize( GetRefDevice(), - rParaPortion.GetNode()->GetString(), nTmpPos, nPortionLen, &aBuf ); + Size aSize = aTmpFont.QuickGetTextSize(GetRefDevice(), rParaPortion.GetNode()->GetString(), nTmpPos, nPortionLen, &aCharPositionArray, bStacked); + pPortion->SetSize(aSize); } // #i9050# Do Kerning also behind portions... if ( ( aTmpFont.GetFixKerning() > 0 ) && ( ( nTmpPos + nPortionLen ) < pNode->Len() ) ) - pPortion->GetSize().AdjustWidth(aTmpFont.GetFixKerning() ); + pPortion->adjustSize(aTmpFont.GetFixKerning(), 0); if ( IsFixedCellHeight() ) - pPortion->GetSize().setHeight( ImplCalculateFontIndependentLineSpacing( aTmpFont.GetFontHeight() ) ); + { + pPortion->setHeight( ImplCalculateFontIndependentLineSpacing( aTmpFont.GetFontHeight() ) ); + } // The array is generally flattened at the beginning // => Always simply quick inserts. size_t nPos = nTmpPos - pLine->GetStart(); EditLine::CharPosArrayType& rArray = pLine->GetCharPosArray(); - rArray.insert( rArray.begin() + nPos, aBuf.data(), aBuf.data() + nPortionLen); + assert(aCharPositionArray.get_factor() == 1); + std::vector<sal_Int32>& rKernArray = aCharPositionArray.get_subunit_array(); + rArray.insert( rArray.begin() + nPos, rKernArray.data(), rKernArray.data() + nPortionLen); // And now check for Compression: if ( !bContinueLastPortion && nPortionLen && GetAsianCompressionMode() != CharCompressType::NONE ) { - tools::Long* pDXArray = rArray.data() + nTmpPos - pLine->GetStart(); + sal_Int32* pDXArray = rArray.data() + nTmpPos - pLine->GetStart(); bCompressedChars |= ImplCalcAsianCompression( pNode, pPortion, nTmpPos, pDXArray, 10000, false); } @@ -1190,9 +1328,9 @@ bool ImpEditEngine::CreateLines( sal_Int32 nPara, sal_uInt32 nStartPosY ) // No spacing within L2R/R2L nesting if ( bAllow ) { - tools::Long nExtraSpace = pPortion->GetSize().Height()/5; - nExtraSpace = GetXValue( nExtraSpace ); - pPortion->GetSize().AdjustWidth(nExtraSpace ); + tools::Long nExtraSpace = pPortion->GetSize().Height() / 5; + nExtraSpace = scaleXSpacingValue(nExtraSpace); + pPortion->adjustSize(nExtraSpace, 0); nTmpWidth += nExtraSpace; } } @@ -1203,12 +1341,13 @@ bool ImpEditEngine::CreateLines( sal_Int32 nPara, sal_uInt32 nStartPosY ) tools::Long nWidthAfterTab = 0; for ( sal_Int32 n = aCurrentTab.nTabPortion+1; n <= nTmpPortion; n++ ) { - const TextPortion& rTP = rParaPortion.GetTextPortions()[n]; - nWidthAfterTab += rTP.GetSize().Width(); + const TextPortion& rTextPortion = rParaPortion.GetTextPortions()[n]; + nWidthAfterTab += rTextPortion.GetSize().Width(); } tools::Long nW = nWidthAfterTab; // Length before tab position if ( aCurrentTab.aTabStop.GetAdjustment() == SvxTabAdjust::Right ) { + // Do nothing } else if ( aCurrentTab.aTabStop.GetAdjustment() == SvxTabAdjust::Center ) { @@ -1216,13 +1355,16 @@ bool ImpEditEngine::CreateLines( sal_Int32 nPara, sal_uInt32 nStartPosY ) } else if ( aCurrentTab.aTabStop.GetAdjustment() == SvxTabAdjust::Decimal ) { - OUString aText = GetSelected( EditSelection( EditPaM( rParaPortion.GetNode(), nTmpPos ), - EditPaM( rParaPortion.GetNode(), nTmpPos + nPortionLen ) ) ); + EditPaM aSelectionStart(rParaPortion.GetNode(), nTmpPos); + EditPaM aSelectionEnd(rParaPortion.GetNode(), nTmpPos + nPortionLen); + EditSelection aSelection(aSelectionStart, aSelectionEnd); + OUString aText = GetSelected(aSelection); + sal_Int32 nDecPos = aText.indexOf( aCurrentTab.aTabStop.GetDecimal() ); if ( nDecPos != -1 ) { nW -= rParaPortion.GetTextPortions()[nTmpPortion].GetSize().Width(); - nW += aTmpFont.QuickGetTextSize( GetRefDevice(), rParaPortion.GetNode()->GetString(), nTmpPos, nDecPos ).Width(); + nW += aTmpFont.QuickGetTextSize(GetRefDevice(), rParaPortion.GetNode()->GetString(), nTmpPos, nDecPos, nullptr).Width(); aCurrentTab.bValid = false; } } @@ -1237,14 +1379,14 @@ bool ImpEditEngine::CreateLines( sal_Int32 nPara, sal_uInt32 nStartPosY ) aCurrentTab.bValid = false; } TextPortion& rTabPortion = rParaPortion.GetTextPortions()[aCurrentTab.nTabPortion]; - rTabPortion.GetSize().setWidth( aCurrentTab.nTabPos - aCurrentTab.nStartPosX - nW - nStartX ); + rTabPortion.setWidth( aCurrentTab.nTabPos - aCurrentTab.nStartPosX - nW - nStartX ); nTmpWidth = aCurrentTab.nStartPosX + rTabPortion.GetSize().Width() + nWidthAfterTab; } nTmpPos = nTmpPos + nPortionLen; nPortionEnd = nTmpPos; nTmpPortion++; - if ( aStatus.OneCharPerLine() ) + if (maStatus.OneCharPerLine()) bEOL = true; } @@ -1256,7 +1398,7 @@ bool ImpEditEngine::CreateLines( sal_Int32 nPara, sal_uInt32 nStartPosY ) // this was possibly a portion too far: bool bFixedEnd = false; - if ( aStatus.OneCharPerLine() ) + if (maStatus.OneCharPerLine()) { // State before Portion (apart from nTmpWidth): nTmpPos -= pPortion ? nPortionLen : 0; @@ -1275,7 +1417,7 @@ bool ImpEditEngine::CreateLines( sal_Int32 nPara, sal_uInt32 nStartPosY ) { DBG_ASSERT( pPortion->GetKind() == PortionKind::TEXT, "Len>1, but no TextPortion?" ); nTmpWidth -= pPortion->GetSize().Width(); - sal_Int32 nP = SplitTextPortion( &rParaPortion, nTmpPos, pLine ); + sal_Int32 nP = SplitTextPortion(rParaPortion, nTmpPos, pLine); nTmpWidth += rParaPortion.GetTextPortions()[nP].GetSize().Width(); } } @@ -1316,41 +1458,57 @@ bool ImpEditEngine::CreateLines( sal_Int32 nPara, sal_uInt32 nStartPosY ) bEOL = true; bEOC = true; pLine->SetEnd( nPortionEnd ); - assert( rParaPortion.GetTextPortions().Count() && "No TextPortions?" ); - pLine->SetEndPortion( rParaPortion.GetTextPortions().Count() - 1 ); + assert(rParaPortion.GetTextPortions().Count() && "No TextPortions?"); + pLine->SetEndPortion(rParaPortion.GetTextPortions().Count() - 1); } - if ( aStatus.OneCharPerLine() ) + if (maStatus.OneCharPerLine()) { pLine->SetEnd( nPortionEnd ); pLine->SetEndPortion( nTmpPortion-1 ); } else if ( bFixedEnd ) { - pLine->SetEnd( nPortionStart ); - pLine->SetEndPortion( nTmpPortion-1 ); + if (bFieldStartNextLine) + { + pLine->SetEnd(nPortionStart); + pLine->SetEndPortion(nTmpPortion - 1); + } + else + { + pLine->SetEnd(nPortionStart + 1); + pLine->SetEndPortion(nTmpPortion); + } } else if ( bLineBreak || bBrokenLine ) { - pLine->SetEnd( nPortionStart+1 ); - pLine->SetEndPortion( nTmpPortion-1 ); + if (bFieldStartNextLine) + { + pLine->SetEnd(nPortionStart); + pLine->SetEndPortion(nTmpPortion - 2); + } + else + { + pLine->SetEnd(nPortionStart + 1); + pLine->SetEndPortion(nTmpPortion - 1); + } bEOC = false; // was set above, maybe change the sequence of the if's? } else if ( !bEOL && !bContinueLastPortion ) { DBG_ASSERT( pPortion && ((nPortionEnd-nPortionStart) == pPortion->GetLen()), "However, another portion?!" ); - tools::Long nRemainingWidth = nMaxLineWidth - nTmpWidth; + tools::Long nRemainingWidth = !maStatus.IsSingleLine() ? + nMaxLineWidth - nTmpWidth : pLine->GetCharPosArray()[pLine->GetCharPosArray().size() - 1] + 1; bool bCanHyphenate = ( aTmpFont.GetCharSet() != RTL_TEXTENCODING_SYMBOL ); if ( bCompressedChars && pPortion && ( pPortion->GetLen() > 1 ) && pPortion->GetExtraInfos() && pPortion->GetExtraInfos()->bCompressed ) { // I need the manipulated DXArray for determining the break position... - tools::Long* pDXArray = pLine->GetCharPosArray().data() + (nPortionStart - pLine->GetStart()); + sal_Int32* pDXArray = pLine->GetCharPosArray().data() + (nPortionStart - pLine->GetStart()); ImplCalcAsianCompression( pNode, pPortion, nPortionStart, pDXArray, 10000, true); } if( pPortion ) - ImpBreakLine( &rParaPortion, pLine, pPortion, nPortionStart, - nRemainingWidth, bCanHyphenate && bHyphenatePara ); + ImpBreakLine(rParaPortion, *pLine, pPortion, nPortionStart, nRemainingWidth, bCanHyphenate && bHyphenatePara); } @@ -1358,18 +1516,18 @@ bool ImpEditEngine::CreateLines( sal_Int32 nPara, sal_uInt32 nStartPosY ) // CalcTextSize should be replaced by a continuous registering! - Size aTextSize = pLine->CalcTextSize( rParaPortion ); + Size aTextSize = pLine->CalcTextSize(rParaPortion); if ( aTextSize.Height() == 0 ) { SeekCursor( pNode, pLine->GetStart()+1, aTmpFont ); - aTmpFont.SetPhysFont(*pRefDev); - ImplInitDigitMode(*pRefDev, aTmpFont.GetLanguage()); + aTmpFont.SetPhysFont(*mpRefDev); + ImplInitDigitMode(*mpRefDev, aTmpFont.GetLanguage()); if ( IsFixedCellHeight() ) aTextSize.setHeight( ImplCalculateFontIndependentLineSpacing( aTmpFont.GetFontHeight() ) ); else - aTextSize.setHeight( aTmpFont.GetPhysTxtSize( pRefDev ).Height() ); + aTextSize.setHeight( aTmpFont.GetPhysTxtSize(mpRefDev).Height() ); pLine->SetHeight( static_cast<sal_uInt16>(aTextSize.Height()) ); } @@ -1403,12 +1561,13 @@ bool ImpEditEngine::CreateLines( sal_Int32 nPara, sal_uInt32 nStartPosY ) bSameLineAgain = true; } - - if ( !bSameLineAgain && !aStatus.IsOutliner() ) + if (!bSameLineAgain && !maStatus.IsOutliner()) { if ( rLSItem.GetLineSpaceRule() == SvxLineSpaceRule::Min ) { - sal_uInt16 nMinHeight = GetYValue( rLSItem.GetLineHeight() ); + double fMinHeight = scaleYSpacingValue(rLSItem.GetLineHeight()); + sal_uInt16 nMinHeight = basegfx::fround(fMinHeight); + sal_uInt16 nTxtHeight = pLine->GetHeight(); if ( nTxtHeight < nMinHeight ) { @@ -1420,7 +1579,9 @@ bool ImpEditEngine::CreateLines( sal_Int32 nPara, sal_uInt32 nStartPosY ) } else if ( rLSItem.GetLineSpaceRule() == SvxLineSpaceRule::Fix ) { - sal_uInt16 nFixHeight = GetYValue( rLSItem.GetLineHeight() ); + double fFixHeight = scaleYSpacingValue(rLSItem.GetLineHeight()); + sal_uInt16 nFixHeight = basegfx::fround(fFixHeight); + sal_uInt16 nTxtHeight = pLine->GetHeight(); pLine->SetMaxAscent( static_cast<sal_uInt16>(pLine->GetMaxAscent() + ( nFixHeight - nTxtHeight ) ) ); pLine->SetHeight( nFixHeight, nTxtHeight ); @@ -1429,40 +1590,61 @@ bool ImpEditEngine::CreateLines( sal_Int32 nPara, sal_uInt32 nStartPosY ) { // There are documents with PropLineSpace 0, why? // (cmc: re above question :-) such documents can be seen by importing a .ppt - if ( rLSItem.GetPropLineSpace() && ( rLSItem.GetPropLineSpace() < 100 ) ) + sal_uInt16 nPropLineSpace = rLSItem.GetPropLineSpace(); + double fProportionalScale = double(nPropLineSpace) / 100.0; + constexpr const double f80Percent = 8.0 / 10.0; + double fSpacingFactor = maScalingParameters.fSpacingY; + if (nPropLineSpace && nPropLineSpace < 100) { // Adapted code from sw/source/core/text/itrform2.cxx - sal_uInt16 nPropLineSpace = rLSItem.GetPropLineSpace(); sal_uInt16 nAscent = pLine->GetMaxAscent(); - sal_uInt16 nNewAscent = pLine->GetTxtHeight() * nPropLineSpace / 100 * 4 / 5; // 80% - if ( !nAscent || nAscent > nNewAscent ) - { - pLine->SetMaxAscent( nNewAscent ); - } - sal_uInt16 nHeight = pLine->GetHeight() * nPropLineSpace / 100; - pLine->SetHeight( nHeight, pLine->GetTxtHeight() ); + sal_uInt16 nNewAscent = basegfx::fround(pLine->GetTxtHeight() * fSpacingFactor * fProportionalScale * f80Percent); + if (!nAscent || nAscent > nNewAscent) + pLine->SetMaxAscent(nNewAscent); + sal_uInt16 nHeight = basegfx::fround(pLine->GetHeight() * fProportionalScale * fSpacingFactor); + + pLine->SetHeight(nHeight, pLine->GetTxtHeight()); } - else if ( rLSItem.GetPropLineSpace() && ( rLSItem.GetPropLineSpace() != 100 ) ) + else if (nPropLineSpace && nPropLineSpace != 100) { sal_uInt16 nTxtHeight = pLine->GetHeight(); - sal_Int32 nPropTextHeight = nTxtHeight * rLSItem.GetPropLineSpace() / 100; + sal_Int32 nPropTextHeight = nTxtHeight * fProportionalScale * fSpacingFactor; // The Ascent has to be adjusted for the difference: tools::Long nDiff = pLine->GetHeight() - nPropTextHeight; pLine->SetMaxAscent( static_cast<sal_uInt16>( pLine->GetMaxAscent() - nDiff ) ); pLine->SetHeight( static_cast<sal_uInt16>( nPropTextHeight ), nTxtHeight ); } } + else if (rLSItem.GetInterLineSpaceRule() == SvxInterLineSpaceRule::Off) + { + if (maScalingParameters.fSpacingY < 1.0) + { + double fSpacingFactor = maScalingParameters.fSpacingY; + sal_uInt16 nPropLineSpace = basegfx::fround(100.0 * fSpacingFactor); + if (nPropLineSpace && nPropLineSpace < 100) + { + // Adapted code from sw/source/core/text/itrform2.cxx + sal_uInt16 nAscent = pLine->GetMaxAscent(); + sal_uInt16 nNewAscent = basegfx::fround(pLine->GetTxtHeight() * fSpacingFactor); + if (!nAscent || nAscent > nNewAscent) + pLine->SetMaxAscent(nNewAscent); + sal_uInt16 nHeight = basegfx::fround(pLine->GetHeight() * fSpacingFactor); + + pLine->SetHeight(nHeight, pLine->GetTxtHeight()); + } + + } + } } - if ( ( !IsEffectivelyVertical() && aStatus.AutoPageWidth() ) || - ( IsEffectivelyVertical() && aStatus.AutoPageHeight() ) ) + if ( ( !IsEffectivelyVertical() && maStatus.AutoPageWidth() ) || + ( IsEffectivelyVertical() && maStatus.AutoPageHeight() ) ) { // If the row fits within the current paper width, then this width // has to be used for the Alignment. If it does not fit or if it // will change the paper width, it will be formatted again for // Justification! = LEFT anyway. - tools::Long nMaxLineWidthFix = GetColumnWidth(aPaperSize) - - GetXValue( rLRItem.GetRight() ) - nStartX; + tools::Long nMaxLineWidthFix = GetColumnWidth(maPaperSize) - scaleXSpacingValue(rLRItem.GetRight()) - nStartX; if ( aTextSize.Width() < nMaxLineWidthFix ) nMaxLineWidth = nMaxLineWidthFix; } @@ -1472,8 +1654,8 @@ bool ImpEditEngine::CreateLines( sal_Int32 nPara, sal_uInt32 nStartPosY ) tools::Long nRemainingWidth = nMaxLineWidth - aTextSize.Width(); if ( nRemainingWidth > 0 ) { - ImplExpandCompressedPortions( pLine, &rParaPortion, nRemainingWidth ); - aTextSize = pLine->CalcTextSize( rParaPortion ); + ImplExpandCompressedPortions(*pLine, rParaPortion, nRemainingWidth); + aTextSize = pLine->CalcTextSize(rParaPortion); } } @@ -1485,8 +1667,11 @@ bool ImpEditEngine::CreateLines( sal_Int32 nPara, sal_uInt32 nStartPosY ) TextPortion& rTP = rParaPortion.GetTextPortions()[pLine->GetEndPortion()]; sal_Int32 nPosInArray = pLine->GetEnd()-1-pLine->GetStart(); tools::Long nNewValue = ( nPosInArray ? pLine->GetCharPosArray()[ nPosInArray-1 ] : 0 ) + n; - pLine->GetCharPosArray()[ nPosInArray ] = nNewValue; - rTP.GetSize().AdjustWidth(n ); + if (o3tl::make_unsigned(nPosInArray) < pLine->GetCharPosArray().size()) + { + pLine->GetCharPosArray()[ nPosInArray ] = nNewValue; + } + rTP.adjustSize(n, 0); } pLine->SetTextWidth( aTextSize.Width() ); @@ -1514,7 +1699,7 @@ bool ImpEditEngine::CreateLines( sal_Int32 nPara, sal_uInt32 nStartPosY ) tools::Long nRemainingSpace = nMaxLineWidth - aTextSize.Width(); pLine->SetStartPosX( nStartX ); if ( nRemainingSpace > 0 && (!bEOC || bDistLastLine) ) - ImpAdjustBlocks( &rParaPortion, pLine, nRemainingSpace ); + ImpAdjustBlocks(rParaPortion, *pLine, nRemainingSpace); } break; default: @@ -1548,7 +1733,7 @@ bool ImpEditEngine::CreateLines( sal_Int32 nPara, sal_uInt32 nStartPosY ) } // for <0 think over ! - if ( rParaPortion.IsSimpleInvalid() ) + if (rParaPortion.IsSimpleInvalid()) { // Change through simple Text changes... // Do not cancel formatting since Portions possibly have to be split @@ -1601,6 +1786,7 @@ bool ImpEditEngine::CreateLines( sal_Int32 nPara, sal_uInt32 nStartPosY ) // as nEnd points to the last character! sal_Int32 nEndPortion = pLine->GetEndPortion(); + nCurrentPosY += pLine->GetHeight(); // Next line or maybe a new line... pLine = nullptr; @@ -1611,12 +1797,22 @@ bool ImpEditEngine::CreateLines( sal_Int32 nPara, sal_uInt32 nStartPosY ) nDelFromLine = nLine; break; } + // Stop processing if allowed and this is outside of the paper size height. + // Format at least two lines though, in case something detects whether + // the text has been wrapped or something similar. + if( mbSkipOutsideFormat && nLine > 2 + && !maStatus.AutoPageHeight() && maPaperSize.Height() < nCurrentPosY ) + { + if ( pLine && ( nIndex >= pNode->Len()) ) + nDelFromLine = nLine; + break; + } if ( !pLine ) { if ( nIndex < pNode->Len() ) { pLine = new EditLine; - rParaPortion.GetLines().Insert(++nLine, pLine); + rParaPortion.GetLines().Insert(++nLine, std::unique_ptr<EditLine>(pLine)); } else if ( nIndex && bLineBreak && GetTextRanger() ) { @@ -1625,7 +1821,7 @@ bool ImpEditEngine::CreateLines( sal_Int32 nPara, sal_uInt32 nStartPosY ) TextPortion* pDummyPortion = new TextPortion( 0 ); rParaPortion.GetTextPortions().Append(pDummyPortion); pLine = new EditLine; - rParaPortion.GetLines().Insert(++nLine, pLine); + rParaPortion.GetLines().Insert(++nLine, std::unique_ptr<EditLine>(pLine)); bForceOneRun = true; bProcessingEmptyLine = true; } @@ -1644,66 +1840,63 @@ bool ImpEditEngine::CreateLines( sal_Int32 nPara, sal_uInt32 nStartPosY ) if ( nDelFromLine >= 0 ) rParaPortion.GetLines().DeleteFromLine( nDelFromLine ); - DBG_ASSERT( rParaPortion.GetLines().Count(), "No line after CreateLines!" ); + DBG_ASSERT(rParaPortion.GetLines().Count(), "No line after CreateLines!"); if ( bLineBreak ) - CreateAndInsertEmptyLine( &rParaPortion ); - - bool bHeightChanged = FinishCreateLines( &rParaPortion ); + CreateAndInsertEmptyLine(rParaPortion); - if ( bMapChanged ) - GetRefDevice()->Pop(); + bool bHeightChanged = FinishCreateLines(rParaPortion); GetRefDevice()->Pop(); return bHeightChanged; } -void ImpEditEngine::CreateAndInsertEmptyLine( ParaPortion* pParaPortion ) +void ImpEditEngine::CreateAndInsertEmptyLine(ParaPortion& rParaPortion) { DBG_ASSERT( !GetTextRanger(), "Don't use CreateAndInsertEmptyLine with a polygon!" ); EditLine* pTmpLine = new EditLine; - pTmpLine->SetStart( pParaPortion->GetNode()->Len() ); - pTmpLine->SetEnd( pParaPortion->GetNode()->Len() ); - pParaPortion->GetLines().Append(pTmpLine); + pTmpLine->SetStart(rParaPortion.GetNode()->Len()); + pTmpLine->SetEnd(rParaPortion.GetNode()->Len()); + rParaPortion.GetLines().Append(std::unique_ptr<EditLine>(pTmpLine)); - bool bLineBreak = pParaPortion->GetNode()->Len() > 0; + bool bLineBreak = rParaPortion.GetNode()->Len() > 0; sal_Int32 nSpaceBefore = 0; - sal_Int32 nSpaceBeforeAndMinLabelWidth = GetSpaceBeforeAndMinLabelWidth( pParaPortion->GetNode(), &nSpaceBefore ); - const SvxLRSpaceItem& rLRItem = GetLRSpaceItem( pParaPortion->GetNode() ); - const SvxLineSpacingItem& rLSItem = pParaPortion->GetNode()->GetContentAttribs().GetItem( EE_PARA_SBL ); - tools::Long nStartX = GetXValue( rLRItem.GetTextLeft() + rLRItem.GetTextFirstLineOffset() + nSpaceBefore ); + sal_Int32 nSpaceBeforeAndMinLabelWidth = GetSpaceBeforeAndMinLabelWidth(rParaPortion.GetNode(), &nSpaceBefore); + const SvxLRSpaceItem& rLRItem = GetLRSpaceItem(rParaPortion.GetNode()); + const SvxLineSpacingItem& rLSItem = rParaPortion.GetNode()->GetContentAttribs().GetItem( EE_PARA_SBL ); + tools::Long nStartX = scaleXSpacingValue(rLRItem.GetTextLeft() + rLRItem.GetTextFirstLineOffset() + nSpaceBefore); tools::Rectangle aBulletArea { Point(), Point() }; if ( bLineBreak ) { - nStartX = GetXValue( rLRItem.GetTextLeft() + rLRItem.GetTextFirstLineOffset() + nSpaceBeforeAndMinLabelWidth ); + nStartX = scaleXSpacingValue(rLRItem.GetTextLeft() + rLRItem.GetTextFirstLineOffset() + nSpaceBeforeAndMinLabelWidth); } else { - aBulletArea = GetEditEnginePtr()->GetBulletArea( GetParaPortions().GetPos( pParaPortion ) ); + aBulletArea = GetEditEnginePtr()->GetBulletArea( GetParaPortions().GetPos(&rParaPortion)); if ( !aBulletArea.IsEmpty() && aBulletArea.Right() > 0 ) - pParaPortion->SetBulletX( static_cast<sal_Int32>(GetXValue( aBulletArea.Right() )) ); + rParaPortion.SetBulletX(sal_Int32(scaleXSpacingValue(aBulletArea.Right()))); else - pParaPortion->SetBulletX( 0 ); // If Bullet set incorrectly. - if ( pParaPortion->GetBulletX() > nStartX ) + rParaPortion.SetBulletX( 0 ); // If Bullet set incorrectly. + if (rParaPortion.GetBulletX() > nStartX) { - nStartX = GetXValue( rLRItem.GetTextLeft() + rLRItem.GetTextFirstLineOffset() + nSpaceBeforeAndMinLabelWidth ); - if ( pParaPortion->GetBulletX() > nStartX ) - nStartX = pParaPortion->GetBulletX(); + nStartX = scaleXSpacingValue(rLRItem.GetTextLeft() + rLRItem.GetTextFirstLineOffset() + nSpaceBeforeAndMinLabelWidth); + if (rParaPortion.GetBulletX() > nStartX) + nStartX = rParaPortion.GetBulletX(); } } SvxFont aTmpFont; - SeekCursor( pParaPortion->GetNode(), bLineBreak ? pParaPortion->GetNode()->Len() : 0, aTmpFont ); - aTmpFont.SetPhysFont(*pRefDev); + SeekCursor(rParaPortion.GetNode(), bLineBreak ? rParaPortion.GetNode()->Len() : 0, aTmpFont ); + aTmpFont.SetPhysFont(*mpRefDev); TextPortion* pDummyPortion = new TextPortion( 0 ); - pDummyPortion->GetSize() = aTmpFont.GetPhysTxtSize( pRefDev ); + pDummyPortion->SetSize(aTmpFont.GetPhysTxtSize(mpRefDev)); if ( IsFixedCellHeight() ) - pDummyPortion->GetSize().setHeight( ImplCalculateFontIndependentLineSpacing( aTmpFont.GetFontHeight() ) ); - pParaPortion->GetTextPortions().Append(pDummyPortion); + pDummyPortion->setHeight( ImplCalculateFontIndependentLineSpacing( aTmpFont.GetFontHeight() ) ); + rParaPortion.GetTextPortions().Append(pDummyPortion); FormatterFontMetric aFormatterMetrics; RecalcFormatterFontMetrics( aFormatterMetrics, aTmpFont ); pTmpLine->SetMaxAscent( aFormatterMetrics.nMaxAscent ); @@ -1712,12 +1905,12 @@ void ImpEditEngine::CreateAndInsertEmptyLine( ParaPortion* pParaPortion ) if ( nLineHeight > pTmpLine->GetHeight() ) pTmpLine->SetHeight( nLineHeight ); - if ( !aStatus.IsOutliner() ) + if (!maStatus.IsOutliner()) { - sal_Int32 nPara = GetParaPortions().GetPos( pParaPortion ); + sal_Int32 nPara = GetParaPortions().GetPos(&rParaPortion); SvxAdjust eJustification = GetJustification( nPara ); - tools::Long nMaxLineWidth = GetColumnWidth(aPaperSize); - nMaxLineWidth -= GetXValue( rLRItem.GetRight() ); + tools::Long nMaxLineWidth = GetColumnWidth(maPaperSize); + nMaxLineWidth -= scaleXSpacingValue(rLRItem.GetRight()); if ( nMaxLineWidth < 0 ) nMaxLineWidth = 1; if ( eJustification == SvxAdjust::Center ) @@ -1728,7 +1921,7 @@ void ImpEditEngine::CreateAndInsertEmptyLine( ParaPortion* pParaPortion ) pTmpLine->SetStartPosX( nStartX ); - if ( !aStatus.IsOutliner() ) + if (!maStatus.IsOutliner()) { if ( rLSItem.GetLineSpaceRule() == SvxLineSpaceRule::Min ) { @@ -1752,7 +1945,7 @@ void ImpEditEngine::CreateAndInsertEmptyLine( ParaPortion* pParaPortion ) } else if ( rLSItem.GetInterLineSpaceRule() == SvxInterLineSpaceRule::Prop ) { - sal_Int32 nPara = GetParaPortions().GetPos( pParaPortion ); + sal_Int32 nPara = GetParaPortions().GetPos(&rParaPortion); if ( nPara || pTmpLine->GetStartPortion() ) // Not the very first line { // There are documents with PropLineSpace 0, why? @@ -1789,37 +1982,37 @@ void ImpEditEngine::CreateAndInsertEmptyLine( ParaPortion* pParaPortion ) { // -2: The new one is already inserted. #ifdef DBG_UTIL - EditLine& rLastLine = pParaPortion->GetLines()[pParaPortion->GetLines().Count()-2]; - DBG_ASSERT( rLastLine.GetEnd() == pParaPortion->GetNode()->Len(), "different anyway?" ); + EditLine& rLastLine = rParaPortion.GetLines()[rParaPortion.GetLines().Count()-2]; + DBG_ASSERT( rLastLine.GetEnd() == rParaPortion.GetNode()->Len(), "different anyway?" ); #endif - sal_Int32 nPos = pParaPortion->GetTextPortions().Count() - 1 ; + sal_Int32 nPos = rParaPortion.GetTextPortions().Count() - 1 ; pTmpLine->SetStartPortion( nPos ); pTmpLine->SetEndPortion( nPos ); } } -bool ImpEditEngine::FinishCreateLines( ParaPortion* pParaPortion ) +bool ImpEditEngine::FinishCreateLines(ParaPortion& rParaPortion) { // CalcCharPositions( pParaPortion ); - pParaPortion->SetValid(); - tools::Long nOldHeight = pParaPortion->GetHeight(); - CalcHeight( pParaPortion ); + rParaPortion.SetValid(); + tools::Long nOldHeight = rParaPortion.GetHeight(); + CalcHeight(rParaPortion); - DBG_ASSERT( pParaPortion->GetTextPortions().Count(), "FinishCreateLines: No Text-Portion?" ); - bool bRet = ( pParaPortion->GetHeight() != nOldHeight ); + DBG_ASSERT(rParaPortion.GetTextPortions().Count(), "FinishCreateLines: No Text-Portion?"); + bool bRet = rParaPortion.GetHeight() != nOldHeight; return bRet; } -void ImpEditEngine::ImpBreakLine( ParaPortion* pParaPortion, EditLine* pLine, TextPortion const * pPortion, sal_Int32 nPortionStart, tools::Long nRemainingWidth, bool bCanHyphenate ) +void ImpEditEngine::ImpBreakLine(ParaPortion& rParaPortion, EditLine& rLine, TextPortion const * pPortion, sal_Int32 nPortionStart, tools::Long nRemainingWidth, bool bCanHyphenate) { - ContentNode* const pNode = pParaPortion->GetNode(); + ContentNode* const pNode = rParaPortion.GetNode(); - sal_Int32 nBreakInLine = nPortionStart - pLine->GetStart(); + sal_Int32 nBreakInLine = nPortionStart - rLine.GetStart(); sal_Int32 nMax = nBreakInLine + pPortion->GetLen(); - while ( ( nBreakInLine < nMax ) && ( pLine->GetCharPosArray()[nBreakInLine] < nRemainingWidth ) ) + while ( ( nBreakInLine < nMax ) && ( rLine.GetCharPosArray()[nBreakInLine] < nRemainingWidth ) ) nBreakInLine++; - sal_Int32 nMaxBreakPos = nBreakInLine + pLine->GetStart(); + sal_Int32 nMaxBreakPos = nBreakInLine + rLine.GetStart(); sal_Int32 nBreakPos = SAL_MAX_INT32; bool bCompressBlank = false; @@ -1831,7 +2024,7 @@ void ImpEditEngine::ImpBreakLine( ParaPortion* pParaPortion, EditLine* pLine, Te bool bAltFullRight = false; sal_uInt32 nAltDelChar = 0; - if ( ( nMaxBreakPos < ( nMax + pLine->GetStart() ) ) && ( pNode->GetChar( nMaxBreakPos ) == ' ' ) ) + if ( ( nMaxBreakPos < ( nMax + rLine.GetStart() ) ) && ( pNode->GetChar( nMaxBreakPos ) == ' ' ) ) { // Break behind the blank, blank will be compressed... nBreakPos = nMaxBreakPos + 1; @@ -1839,7 +2032,7 @@ void ImpEditEngine::ImpBreakLine( ParaPortion* pParaPortion, EditLine* pLine, Te } else { - sal_Int32 nMinBreakPos = pLine->GetStart(); + sal_Int32 nMinBreakPos = rLine.GetStart(); const CharAttribList::AttribsType& rAttrs = pNode->GetCharAttribs().GetAttribs(); for (size_t nAttr = rAttrs.size(); nAttr; ) { @@ -1877,9 +2070,20 @@ void ImpEditEngine::ImpBreakLine( ParaPortion* pParaPortion, EditLine* pLine, Te aUserOptions.allowPunctuationOutsideMargin = bAllowPunctuationOutsideMargin; aUserOptions.allowHyphenateEnglish = false; - i18n::LineBreakResults aLBR = _xBI->getLineBreak( - pNode->GetString(), nMaxBreakPos, aLocale, nMinBreakPos, aHyphOptions, aUserOptions ); - nBreakPos = aLBR.breakIndex; + if (!maStatus.IsSingleLine()) + { + i18n::LineBreakResults aLBR = _xBI->getLineBreak( + pNode->GetString(), nMaxBreakPos, aLocale, nMinBreakPos, aHyphOptions, aUserOptions ); + nBreakPos = aLBR.breakIndex; + + // show soft hyphen + if ( nBreakPos && CH_SOFTHYPHEN == pNode->GetString()[ sal_Int32(nBreakPos) - 1 ] ) + bHyphenated = true; + } + else + { + nBreakPos = nMaxBreakPos; + } // BUG in I18N - under special condition (break behind field, #87327#) breakIndex is < nMinBreakPos if ( nBreakPos < nMinBreakPos ) @@ -1906,7 +2110,7 @@ void ImpEditEngine::ImpBreakLine( ParaPortion* pParaPortion, EditLine* pLine, Te } bHangingPunctuation = nBreakPos > nMaxBreakPos; - pLine->SetHangingPunctuation( bHangingPunctuation ); + rLine.SetHangingPunctuation( bHangingPunctuation ); // Whether a separator or not, push the word after the separator through // hyphenation... NMaxBreakPos is the last character that fits into @@ -1928,14 +2132,14 @@ void ImpEditEngine::ImpBreakLine( ParaPortion* pParaPortion, EditLine* pLine, Te const OUString aWord = pNode->GetString().copy(nWordStart, nWordLen); sal_Int32 nMinTrail = nWordEnd-nMaxBreakPos+1; //+1: Before the dickey letter Reference< XHyphenatedWord > xHyphWord; - if (xHyphenator.is()) - xHyphWord = xHyphenator->hyphenate( aWord, aLocale, aWord.getLength() - nMinTrail, Sequence< PropertyValue >() ); + if (mxHyphenator.is()) + xHyphWord = mxHyphenator->hyphenate( aWord, aLocale, aWord.getLength() - nMinTrail, Sequence< PropertyValue >() ); if (xHyphWord.is()) { bool bAlternate = xHyphWord->isAlternativeSpelling(); sal_Int32 _nWordLen = 1 + xHyphWord->getHyphenPos(); - if ( ( _nWordLen >= 2 ) && ( (nWordStart+_nWordLen) >= (pLine->GetStart() + 2 ) ) ) + if ( ( _nWordLen >= 2 ) && ( (nWordStart+_nWordLen) >= (rLine.GetStart() + 2 ) ) ) { if ( !bAlternate ) { @@ -1946,8 +2150,8 @@ void ImpEditEngine::ImpBreakLine( ParaPortion* pParaPortion, EditLine* pLine, Te { // TODO: handle all alternative hyphenations (see hyphen-1.2.8/tests/unicode.*) OUString aAlt( xHyphWord->getHyphenatedWord() ); - OUString aAltLeft(aAlt.copy(0, _nWordLen)); - OUString aAltRight(aAlt.copy(_nWordLen)); + std::u16string_view aAltLeft(aAlt.subView(0, _nWordLen)); + std::u16string_view aAltRight(aAlt.subView(_nWordLen)); bAltFullLeft = aWord.startsWith(aAltLeft); bAltFullRight = aWord.endsWith(aAltRight); nAltDelChar = aWord.getLength() - aAlt.getLength() + static_cast<int>(!bAltFullLeft) + static_cast<int>(!bAltFullRight); @@ -2006,39 +2210,42 @@ void ImpEditEngine::ImpBreakLine( ParaPortion* pParaPortion, EditLine* pLine, Te } } - if ( nBreakPos <= pLine->GetStart() ) + if ( nBreakPos <= rLine.GetStart() ) { // No separator in line => Chop! nBreakPos = nMaxBreakPos; // I18N nextCharacters ! - if ( nBreakPos <= pLine->GetStart() ) - nBreakPos = pLine->GetStart() + 1; // Otherwise infinite loop! + if ( nBreakPos <= rLine.GetStart() ) + nBreakPos = rLine.GetStart() + 1; // Otherwise infinite loop! } } // the dickey portion is the end portion - pLine->SetEnd( nBreakPos ); + rLine.SetEnd( nBreakPos ); - sal_Int32 nEndPortion = SplitTextPortion( pParaPortion, nBreakPos, pLine ); + sal_Int32 nEndPortion = SplitTextPortion(rParaPortion, nBreakPos, &rLine); if ( !bCompressBlank && !bHangingPunctuation ) { // When justification is not SvxAdjust::Left, it's important to compress // the trailing space even if there is enough room for the space... // Don't check for SvxAdjust::Left, doesn't matter to compress in this case too... - assert( nBreakPos > pLine->GetStart() && "ImpBreakLines - BreakPos not expected!" ); + assert( nBreakPos > rLine.GetStart() && "ImpBreakLines - BreakPos not expected!" ); if ( pNode->GetChar( nBreakPos-1 ) == ' ' ) bCompressBlank = true; } if ( bCompressBlank || bHangingPunctuation ) { - TextPortion& rTP = pParaPortion->GetTextPortions()[nEndPortion]; + TextPortion& rTP = rParaPortion.GetTextPortions()[nEndPortion]; DBG_ASSERT( rTP.GetKind() == PortionKind::TEXT, "BlankRubber: No TextPortion!" ); - DBG_ASSERT( nBreakPos > pLine->GetStart(), "SplitTextPortion at the beginning of the line?" ); - sal_Int32 nPosInArray = nBreakPos - 1 - pLine->GetStart(); - rTP.GetSize().setWidth( ( nPosInArray && ( rTP.GetLen() > 1 ) ) ? pLine->GetCharPosArray()[ nPosInArray-1 ] : 0 ); - pLine->GetCharPosArray()[ nPosInArray ] = rTP.GetSize().Width(); + DBG_ASSERT( nBreakPos > rLine.GetStart(), "SplitTextPortion at the beginning of the line?" ); + sal_Int32 nPosInArray = nBreakPos - 1 - rLine.GetStart(); + rTP.setWidth( ( nPosInArray && ( rTP.GetLen() > 1 ) ) ? rLine.GetCharPosArray()[ nPosInArray-1 ] : 0 ); + if (o3tl::make_unsigned(nPosInArray) < rLine.GetCharPosArray().size()) + { + rLine.GetCharPosArray()[ nPosInArray ] = rTP.GetSize().Width(); + } } else if ( bHyphenated ) { @@ -2047,51 +2254,55 @@ void ImpEditEngine::ImpBreakLine( ParaPortion* pParaPortion, EditLine* pLine, Te pHyphPortion->SetKind( PortionKind::HYPHENATOR ); if ( (cAlternateReplChar || cAlternateExtraChar) && bAltFullRight ) // alternation after the break doesn't supported { - TextPortion& rPrev = pParaPortion->GetTextPortions()[nEndPortion]; + TextPortion& rPrev = rParaPortion.GetTextPortions()[nEndPortion]; DBG_ASSERT( rPrev.GetLen(), "Hyphenate: Prev portion?!" ); rPrev.SetLen( rPrev.GetLen() - nAltDelChar ); pHyphPortion->SetLen( nAltDelChar ); if (cAlternateReplChar && !bAltFullLeft) pHyphPortion->SetExtraValue( cAlternateReplChar ); // Correct width of the portion above: - rPrev.GetSize().setWidth( - pLine->GetCharPosArray()[ nBreakPos-1 - pLine->GetStart() - nAltDelChar ] ); + rPrev.setWidth( + rLine.GetCharPosArray()[ nBreakPos-1 - rLine.GetStart() - nAltDelChar ] ); } // Determine the width of the Hyph-Portion: SvxFont aFont; - SeekCursor( pParaPortion->GetNode(), nBreakPos, aFont ); + SeekCursor(rParaPortion.GetNode(), nBreakPos, aFont); aFont.SetPhysFont(*GetRefDevice()); - pHyphPortion->GetSize().setHeight( GetRefDevice()->GetTextHeight() ); - pHyphPortion->GetSize().setWidth( GetRefDevice()->GetTextWidth( CH_HYPH ) ); + pHyphPortion->SetSize(Size(GetRefDevice()->GetTextWidth(CH_HYPH), GetRefDevice()->GetTextHeight())); - pParaPortion->GetTextPortions().Insert(++nEndPortion, pHyphPortion); + rParaPortion.GetTextPortions().Insert(++nEndPortion, pHyphPortion); } - pLine->SetEndPortion( nEndPortion ); + rLine.SetEndPortion( nEndPortion ); } -void ImpEditEngine::ImpAdjustBlocks( ParaPortion* pParaPortion, EditLine* pLine, tools::Long nRemainingSpace ) +void ImpEditEngine::ImpAdjustBlocks(ParaPortion& rParaPortion, EditLine& rLine, tools::Long nRemainingSpace ) { DBG_ASSERT( nRemainingSpace > 0, "AdjustBlocks: Somewhat too little..." ); - assert( pLine && "AdjustBlocks: Line ?!" ); - if ( ( nRemainingSpace < 0 ) || pLine->IsEmpty() ) + + if ( ( nRemainingSpace < 0 ) || rLine.IsEmpty() ) return ; - const sal_Int32 nFirstChar = pLine->GetStart(); - const sal_Int32 nLastChar = pLine->GetEnd() -1; // Last points behind - ContentNode* pNode = pParaPortion->GetNode(); + const sal_Int32 nFirstChar = rLine.GetStart(); + const sal_Int32 nLastChar = rLine.GetEnd() -1; // Last points behind + ContentNode* pNode = rParaPortion.GetNode(); DBG_ASSERT( nLastChar < pNode->Len(), "AdjustBlocks: Out of range!" ); // Search blanks or Kashidas... std::vector<sal_Int32> aPositions; + + // Kashidas ? + ImpFindKashidas( pNode, nFirstChar, nLastChar, aPositions ); + auto nKashidas = aPositions.size(); + sal_uInt16 nLastScript = i18n::ScriptType::LATIN; for ( sal_Int32 nChar = nFirstChar; nChar <= nLastChar; nChar++ ) { EditPaM aPaM( pNode, nChar+1 ); - LanguageType eLang = GetLanguage(aPaM); + LanguageType eLang = GetLanguage(aPaM).nLang; sal_uInt16 nScript = GetI18NScriptType(aPaM); - if ( MsLangId::getPrimaryLanguage( eLang) == LANGUAGE_ARABIC_PRIMARY_ONLY ) - // Arabic script is handled later. + // Arabic script is handled above, but if no Kashida positions are found, use blanks. + if (MsLangId::getPrimaryLanguage(eLang) == LANGUAGE_ARABIC_PRIMARY_ONLY && nKashidas) continue; if ( pNode->GetChar(nChar) == ' ' ) @@ -2117,9 +2328,6 @@ void ImpEditEngine::ImpAdjustBlocks( ParaPortion* pParaPortion, EditLine* pLine, nLastScript = nScript; } - // Kashidas ? - ImpFindKashidas( pNode, nFirstChar, nLastChar, aPositions ); - if ( aPositions.empty() ) return; @@ -2127,26 +2335,26 @@ void ImpEditEngine::ImpAdjustBlocks( ParaPortion* pParaPortion, EditLine* pLine, // The width must be distributed to the blockers in front... // But not if it is the only one. if ( ( pNode->GetChar( nLastChar ) == ' ' ) && ( aPositions.size() > 1 ) && - ( MsLangId::getPrimaryLanguage( GetLanguage( EditPaM( pNode, nLastChar ) ) ) != LANGUAGE_ARABIC_PRIMARY_ONLY ) ) + ( MsLangId::getPrimaryLanguage( GetLanguage( EditPaM( pNode, nLastChar ) ).nLang ) != LANGUAGE_ARABIC_PRIMARY_ONLY ) ) { aPositions.pop_back(); sal_Int32 nPortionStart, nPortion; - nPortion = pParaPortion->GetTextPortions().FindPortion( nLastChar+1, nPortionStart ); - TextPortion& rLastPortion = pParaPortion->GetTextPortions()[ nPortion ]; - tools::Long nRealWidth = pLine->GetCharPosArray()[nLastChar-nFirstChar]; + nPortion = rParaPortion.GetTextPortions().FindPortion( nLastChar+1, nPortionStart ); + TextPortion& rLastPortion = rParaPortion.GetTextPortions()[ nPortion ]; + tools::Long nRealWidth = rLine.GetCharPosArray()[nLastChar-nFirstChar]; tools::Long nBlankWidth = nRealWidth; if ( nLastChar > nPortionStart ) - nBlankWidth -= pLine->GetCharPosArray()[nLastChar-nFirstChar-1]; + nBlankWidth -= rLine.GetCharPosArray()[nLastChar-nFirstChar-1]; // Possibly the blank has already been deducted in ImpBreakLine: if ( nRealWidth == rLastPortion.GetSize().Width() ) { // For the last character the portion must stop behind the blank // => Simplify correction: DBG_ASSERT( ( nPortionStart + rLastPortion.GetLen() ) == ( nLastChar+1 ), "Blank actually not at the end of the portion!?"); - rLastPortion.GetSize().AdjustWidth( -nBlankWidth ); + rLastPortion.adjustSize(-nBlankWidth, 0); nRemainingSpace += nBlankWidth; } - pLine->GetCharPosArray()[nLastChar-nFirstChar] -= nBlankWidth; + rLine.GetCharPosArray()[nLastChar-nFirstChar] -= nBlankWidth; } size_t nGaps = aPositions.size(); @@ -2156,6 +2364,19 @@ void ImpEditEngine::ImpAdjustBlocks( ParaPortion* pParaPortion, EditLine* pLine, DBG_ASSERT( nSomeExtraSpace < static_cast<tools::Long>(nGaps), "AdjustBlocks: ExtraSpace too large" ); DBG_ASSERT( nSomeExtraSpace >= 0, "AdjustBlocks: ExtraSpace < 0 " ); + // Mark Kashida positions, so that VCL knows where to insert Kashida and + // where to only expand the width. + if (nKashidas) + { + rLine.GetKashidaArray().resize(rLine.GetCharPosArray().size(), false); + for (size_t i = 0; i < nKashidas; i++) + { + auto nChar = aPositions[i]; + if ( nChar < nLastChar ) + rLine.GetKashidaArray()[nChar-nFirstChar] = 1 /*sal_True*/; + } + } + // Correct the positions in the Array and the portion widths: // Last character won't be considered... for (auto const& nChar : aPositions) @@ -2163,22 +2384,23 @@ void ImpEditEngine::ImpAdjustBlocks( ParaPortion* pParaPortion, EditLine* pLine, if ( nChar < nLastChar ) { sal_Int32 nPortionStart, nPortion; - nPortion = pParaPortion->GetTextPortions().FindPortion( nChar, nPortionStart, true ); - TextPortion& rLastPortion = pParaPortion->GetTextPortions()[ nPortion ]; + nPortion = rParaPortion.GetTextPortions().FindPortion( nChar, nPortionStart, true ); + TextPortion& rLastPortion = rParaPortion.GetTextPortions()[ nPortion ]; // The width of the portion: - rLastPortion.GetSize().AdjustWidth(nMore4Everyone ); - if ( nSomeExtraSpace ) - rLastPortion.GetSize().AdjustWidth( 1 ); + rLastPortion.adjustSize(nMore4Everyone, 0); + if (nSomeExtraSpace) + { + rLastPortion.adjustSize(1, 0); + } // Correct positions in array - // Even for kashidas just change positions, VCL will then draw the kashida automatically sal_Int32 nPortionEnd = nPortionStart + rLastPortion.GetLen(); for ( sal_Int32 _n = nChar; _n < nPortionEnd; _n++ ) { - pLine->GetCharPosArray()[_n-nFirstChar] += nMore4Everyone; + rLine.GetCharPosArray()[_n-nFirstChar] += nMore4Everyone; if ( nSomeExtraSpace ) - pLine->GetCharPosArray()[_n-nFirstChar]++; + rLine.GetCharPosArray()[_n-nFirstChar]++; } if ( nSomeExtraSpace ) @@ -2187,11 +2409,18 @@ void ImpEditEngine::ImpAdjustBlocks( ParaPortion* pParaPortion, EditLine* pLine, } // Now the text width contains the extra width... - pLine->SetTextWidth( pLine->GetTextWidth() + nRemainingSpace ); + rLine.SetTextWidth(rLine.GetTextWidth() + nRemainingSpace); } +// For Kashidas from sw/source/core/text/porlay.cxx void ImpEditEngine::ImpFindKashidas( ContentNode* pNode, sal_Int32 nStart, sal_Int32 nEnd, std::vector<sal_Int32>& rArray ) { + // Kashida glyph looks suspicious, skip Kashida justification + if (GetRefDevice()->GetMinKashida() <= 0) + return; + + std::vector<sal_Int32> aKashidaArray; + // the search has to be performed on a per word base EditSelection aWordSel( EditPaM( pNode, nStart ) ); @@ -2210,12 +2439,20 @@ void ImpEditEngine::ImpFindKashidas( ContentNode* pNode, sal_Int32 nStart, sal_I // restore selection for proper iteration at the end of the function aWordSel.Max().SetIndex( nSavPos ); - sal_Int32 nIdx = 0; + sal_Int32 nIdx = 0, nPrevIdx = 0; sal_Int32 nKashidaPos = -1; - sal_Unicode cCh; - sal_Unicode cPrevCh = 0; + sal_Unicode cCh, cPrevCh = 0; + + int nPriorityLevel = 7; // 0..6 = level found + // 7 not found - while ( nIdx < aWord.getLength() ) + sal_Int32 nWordLen = aWord.getLength(); + + // ignore trailing vowel chars + while( nWordLen && isTransparentChar( aWord[ nWordLen - 1 ] )) + --nWordLen; + + while ( nIdx < nWordLen ) { cCh = aWord[ nIdx ]; @@ -2224,104 +2461,163 @@ void ImpEditEngine::ImpFindKashidas( ContentNode* pNode, sal_Int32 nStart, sal_I if ( 0x640 == cCh ) { nKashidaPos = aWordSel.Min().GetIndex() + nIdx; - break; + nPriorityLevel = 0; } // 2. Priority: // after a Seen or Sad - if ( nIdx + 1 < aWord.getLength() && - ( 0x633 == cCh || 0x635 == cCh ) ) + if (nPriorityLevel >= 1 && nIdx < nWordLen - 1) { - nKashidaPos = aWordSel.Min().GetIndex() + nIdx; - break; + if( isSeenOrSadChar( cCh ) + && (aWord[ nIdx+1 ] != 0x200C) ) // #i98410#: prevent ZWNJ expansion + { + nKashidaPos = aWordSel.Min().GetIndex() + nIdx; + nPriorityLevel = 1; + } } // 3. Priority: - // before final form of the Marbuta, Hah, Dal - // 4. Priority: - // before final form of Alef, Lam or Kaf - if ( nIdx && nIdx + 1 == aWord.getLength() && - ( 0x629 == cCh || 0x62D == cCh || 0x62F == cCh || - 0x627 == cCh || 0x644 == cCh || 0x643 == cCh ) ) + // before final form of Teh Marbuta, Heh, Dal + if ( nPriorityLevel >= 2 && nIdx > 0 ) { - DBG_ASSERT( 0 != cPrevCh, "No previous character" ); + if ( isTehMarbutaChar ( cCh ) || // Teh Marbuta (right joining) + isDalChar ( cCh ) || // Dal (right joining) final form may appear in the middle of word + ( isHehChar ( cCh ) && nIdx == nWordLen - 1)) // Heh (dual joining) only at end of word + { - // check if character is connectable to previous character, - if ( lcl_ConnectToPrev( cCh, cPrevCh ) ) + SAL_WARN_IF( 0 == cPrevCh, "editeng", "No previous character" ); + // check if character is connectable to previous character, + if ( lcl_ConnectToPrev( cCh, cPrevCh ) ) + { + nKashidaPos = aWordSel.Min().GetIndex() + nPrevIdx; + nPriorityLevel = 2; + } + } + } + + // 4. Priority: + // before final form of Alef, Tah, Lam, Kaf or Gaf + if ( nPriorityLevel >= 3 && nIdx > 0 ) + { + if ( isAlefChar ( cCh ) || // Alef (right joining) final form may appear in the middle of word + (( isLamChar ( cCh ) || // Lam, + isTahChar ( cCh ) || // Tah, + isKafChar ( cCh ) || // Kaf (all dual joining) + isGafChar ( cCh ) ) + && nIdx == nWordLen - 1)) // only at end of word { - nKashidaPos = aWordSel.Min().GetIndex() + nIdx - 1; - break; + SAL_WARN_IF( 0 == cPrevCh, "editeng", "No previous character" ); + // check if character is connectable to previous character, + if ( lcl_ConnectToPrev( cCh, cPrevCh ) ) + { + nKashidaPos = aWordSel.Min().GetIndex() + nPrevIdx; + nPriorityLevel = 3; + } } } // 5. Priority: - // before media Bah - if ( nIdx && nIdx + 1 < aWord.getLength() && 0x628 == cCh ) + // before medial Beh-like + if ( nPriorityLevel >= 4 && nIdx > 0 && nIdx < nWordLen - 1 ) { - DBG_ASSERT( 0 != cPrevCh, "No previous character" ); - - // check if next character is Reh, Yeh or Alef Maksura - sal_Unicode cNextCh = aWord[ nIdx + 1 ]; + if ( isBehChar ( cCh ) ) + { + // check if next character is Reh or Yeh-like + sal_Unicode cNextCh = aWord[ nIdx + 1 ]; + if ( isRehChar ( cNextCh ) || isYehChar ( cNextCh )) + { + SAL_WARN_IF( 0 == cPrevCh, "editeng", "No previous character" ); + // check if character is connectable to previous character, + if ( lcl_ConnectToPrev( cCh, cPrevCh ) ) + { + nKashidaPos = aWordSel.Min().GetIndex() + nPrevIdx; + nPriorityLevel = 4; + } + } + } + } - if ( 0x631 == cNextCh || 0x64A == cNextCh || - 0x649 == cNextCh ) + // 6. Priority: + // before the final form of Waw, Ain, Qaf and Feh + if ( nPriorityLevel >= 5 && nIdx > 0 ) + { + if ( isWawChar ( cCh ) || // Wav (right joining) + // final form may appear in the middle of word + (( isAinChar ( cCh ) || // Ain (dual joining) + isQafChar ( cCh ) || // Qaf (dual joining) + isFehChar ( cCh ) ) // Feh (dual joining) + && nIdx == nWordLen - 1)) // only at end of word { + SAL_WARN_IF( 0 == cPrevCh, "editeng", "No previous character" ); // check if character is connectable to previous character, if ( lcl_ConnectToPrev( cCh, cPrevCh ) ) - nKashidaPos = aWordSel.Min().GetIndex() + nIdx - 1; + { + nKashidaPos = aWordSel.Min().GetIndex() + nPrevIdx; + nPriorityLevel = 5; + } } } - // 6. Priority: // other connecting possibilities - if ( nIdx && nIdx + 1 == aWord.getLength() && - 0x60C <= cCh && 0x6FE >= cCh ) + if ( nPriorityLevel >= 6 && nIdx > 0 ) { - DBG_ASSERT( 0 != cPrevCh, "No previous character" ); - - // check if character is connectable to previous character, - if ( lcl_ConnectToPrev( cCh, cPrevCh ) ) + // Reh, Zain + if ( isRehChar ( cCh ) ) { - // only choose this position if we did not find - // a better one: - if ( nKashidaPos<0 ) - nKashidaPos = aWordSel.Min().GetIndex() + nIdx - 1; - break; + SAL_WARN_IF( 0 == cPrevCh, "editeng", "No previous character" ); + // check if character is connectable to previous character, + if ( lcl_ConnectToPrev( cCh, cPrevCh ) ) + { + nKashidaPos = aWordSel.Min().GetIndex() + nPrevIdx; + nPriorityLevel = 6; + } } } - // Do not consider Fathatan, Dammatan, Kasratan, Fatha, - // Damma, Kasra, Shadda and Sukun when checking if - // a character can be connected to previous character. - if ( cCh < 0x64B || cCh > 0x652 ) + // Do not consider vowel marks when checking if a character + // can be connected to previous character. + if ( !isTransparentChar ( cCh) ) + { cPrevCh = cCh; + nPrevIdx = nIdx; + } ++nIdx; } // end of current word if ( nKashidaPos>=0 ) - rArray.push_back( nKashidaPos ); + aKashidaArray.push_back( nKashidaPos ); aWordSel = WordRight( aWordSel.Max(), css::i18n::WordType::DICTIONARY_WORD ); aWordSel = SelectWord( aWordSel, css::i18n::WordType::DICTIONARY_WORD ); } + + // Validate + std::vector<sal_Int32> aDropped(aKashidaArray.size()); + auto nOldLayout = GetRefDevice()->GetLayoutMode(); + GetRefDevice()->SetLayoutMode(nOldLayout | vcl::text::ComplexTextLayoutFlags::BiDiRtl); + GetRefDevice()->ValidateKashidas(pNode->GetString(), nStart, nEnd - nStart, + aKashidaArray.size(), aKashidaArray.data(), aDropped.data()); + GetRefDevice()->SetLayoutMode(nOldLayout); + + for (auto const& pos : aKashidaArray) + if (std::find(aDropped.begin(), aDropped.end(), pos) == aDropped.end()) + rArray.push_back(pos); } -sal_Int32 ImpEditEngine::SplitTextPortion( ParaPortion* pPortion, sal_Int32 nPos, EditLine* pCurLine ) +sal_Int32 ImpEditEngine::SplitTextPortion(ParaPortion& rParaPortion, sal_Int32 nPos, EditLine* pCurLine) { // The portion at nPos is split, if there is not a transition at nPos anyway if ( nPos == 0 ) return 0; - assert( pPortion && "SplitTextPortion: Which ?" ); - sal_Int32 nSplitPortion; sal_Int32 nTmpPos = 0; TextPortion* pTextPortion = nullptr; - sal_Int32 nPortions = pPortion->GetTextPortions().Count(); + sal_Int32 nPortions = rParaPortion.GetTextPortions().Count(); for ( nSplitPortion = 0; nSplitPortion < nPortions; nSplitPortion++ ) { - TextPortion& rTP = pPortion->GetTextPortions()[nSplitPortion]; + TextPortion& rTP = rParaPortion.GetTextPortions()[nSplitPortion]; nTmpPos = nTmpPos + rTP.GetLen(); if ( nTmpPos >= nPos ) { @@ -2344,44 +2640,45 @@ sal_Int32 ImpEditEngine::SplitTextPortion( ParaPortion* pPortion, sal_Int32 nPos sal_Int32 nOverlapp = nTmpPos - nPos; pTextPortion->SetLen( pTextPortion->GetLen() - nOverlapp ); TextPortion* pNewPortion = new TextPortion( nOverlapp ); - pPortion->GetTextPortions().Insert(nSplitPortion+1, pNewPortion); + rParaPortion.GetTextPortions().Insert(nSplitPortion+1, pNewPortion); // Set sizes if ( pCurLine ) { // No new GetTextSize, instead use values from the Array: assert( nPos > pCurLine->GetStart() && "SplitTextPortion at the beginning of the line?" ); - pTextPortion->GetSize().setWidth( pCurLine->GetCharPosArray()[ nPos-pCurLine->GetStart()-1 ] ); + pTextPortion->setWidth(pCurLine->GetCharPosArray()[nPos - pCurLine->GetStart() - 1]); if ( pTextPortion->GetExtraInfos() && pTextPortion->GetExtraInfos()->bCompressed ) { // We need the original size from the portion - sal_Int32 nTxtPortionStart = pPortion->GetTextPortions().GetStartPos( nSplitPortion ); - SvxFont aTmpFont( pPortion->GetNode()->GetCharAttribs().GetDefFont() ); - SeekCursor( pPortion->GetNode(), nTxtPortionStart+1, aTmpFont ); + sal_Int32 nTxtPortionStart = rParaPortion.GetTextPortions().GetStartPos( nSplitPortion ); + SvxFont aTmpFont = rParaPortion.GetNode()->GetCharAttribs().GetDefFont(); + SeekCursor(rParaPortion.GetNode(), nTxtPortionStart + 1, aTmpFont); aTmpFont.SetPhysFont(*GetRefDevice()); GetRefDevice()->Push( vcl::PushFlags::TEXTLANGUAGE ); ImplInitDigitMode(*GetRefDevice(), aTmpFont.GetLanguage()); - Size aSz = aTmpFont.QuickGetTextSize( GetRefDevice(), pPortion->GetNode()->GetString(), nTxtPortionStart, pTextPortion->GetLen() ); + Size aSz = aTmpFont.QuickGetTextSize( GetRefDevice(), rParaPortion.GetNode()->GetString(), + nTxtPortionStart, pTextPortion->GetLen(), nullptr ); GetRefDevice()->Pop(); pTextPortion->GetExtraInfos()->nOrgWidth = aSz.Width(); } } else - pTextPortion->GetSize().setWidth( -1 ); + pTextPortion->setWidth(-1); return nSplitPortion; } -void ImpEditEngine::CreateTextPortions( ParaPortion* pParaPortion, sal_Int32& rStart ) +void ImpEditEngine::CreateTextPortions(ParaPortion& rParaPortion, sal_Int32& rStart) { sal_Int32 nStartPos = rStart; - ContentNode* pNode = pParaPortion->GetNode(); + ContentNode* pNode = rParaPortion.GetNode(); DBG_ASSERT( pNode->Len(), "CreateTextPortions should not be used for empty paragraphs!" ); o3tl::sorted_vector< sal_Int32 > aPositions; aPositions.insert( 0 ); - for (sal_uInt16 nAttr = 0;; ++nAttr) + for (std::size_t nAttr = 0;; ++nAttr) { // Insert Start and End into the Array... // The Insert method does not allow for duplicate values... @@ -2393,15 +2690,13 @@ void ImpEditEngine::CreateTextPortions( ParaPortion* pParaPortion, sal_Int32& rS } aPositions.insert( pNode->Len() ); - if ( pParaPortion->aScriptInfos.empty() ) - InitScriptTypes( GetParaPortions().GetPos( pParaPortion ) ); + if (rParaPortion.getScriptTypePosInfos().empty()) + InitScriptTypes(GetParaPortions().GetPos(&rParaPortion)); - const ScriptTypePosInfos& rTypes = pParaPortion->aScriptInfos; - for (const ScriptTypePosInfo& rType : rTypes) + for (const ScriptTypePosInfo& rType : rParaPortion.getScriptTypePosInfos()) aPositions.insert( rType.nStartPos ); - const WritingDirectionInfos& rWritingDirections = pParaPortion->aWritingDirectionInfos; - for (const WritingDirectionInfo & rWritingDirection : rWritingDirections) + for (const WritingDirectionInfo& rWritingDirection : rParaPortion.getWritingDirectionInfos()) aPositions.insert( rWritingDirection.nStartPos ); if ( mpIMEInfos && mpIMEInfos->nLen && mpIMEInfos->pAttribs && ( mpIMEInfos->aPos.GetNode() == pNode ) ) @@ -2424,9 +2719,9 @@ void ImpEditEngine::CreateTextPortions( ParaPortion* pParaPortion, sal_Int32& rS sal_Int32 nPortionStart = 0; sal_Int32 nInvPortion = 0; sal_Int32 nP; - for ( nP = 0; nP < pParaPortion->GetTextPortions().Count(); nP++ ) + for ( nP = 0; nP < rParaPortion.GetTextPortions().Count(); nP++ ) { - const TextPortion& rTmpPortion = pParaPortion->GetTextPortions()[nP]; + const TextPortion& rTmpPortion = rParaPortion.GetTextPortions()[nP]; nPortionStart = nPortionStart + rTmpPortion.GetLen(); if ( nPortionStart >= nStartPos ) { @@ -2436,16 +2731,16 @@ void ImpEditEngine::CreateTextPortions( ParaPortion* pParaPortion, sal_Int32& rS break; } } - DBG_ASSERT( nP < pParaPortion->GetTextPortions().Count() || !pParaPortion->GetTextPortions().Count(), "Nothing to delete: CreateTextPortions" ); - if ( nInvPortion && ( nPortionStart+pParaPortion->GetTextPortions()[nInvPortion].GetLen() > nStartPos ) ) + DBG_ASSERT( nP < rParaPortion.GetTextPortions().Count() || !rParaPortion.GetTextPortions().Count(), "Nothing to delete: CreateTextPortions" ); + if ( nInvPortion && ( nPortionStart + rParaPortion.GetTextPortions()[nInvPortion].GetLen() > nStartPos ) ) { // prefer one in front... // But only if it was in the middle of the portion of, otherwise it // might be the only one in the row in front! nInvPortion--; - nPortionStart = nPortionStart - pParaPortion->GetTextPortions()[nInvPortion].GetLen(); + nPortionStart = nPortionStart - rParaPortion.GetTextPortions()[nInvPortion].GetLen(); } - pParaPortion->GetTextPortions().DeleteFromPortion( nInvPortion ); + rParaPortion.GetTextPortions().DeleteFromPortion( nInvPortion ); // A portion may also have been formed by a line break: aPositions.insert( nPortionStart ); @@ -2458,21 +2753,21 @@ void ImpEditEngine::CreateTextPortions( ParaPortion* pParaPortion, sal_Int32& rS while ( i != aPositions.end() ) { TextPortion* pNew = new TextPortion( (*i++) - *nInvPos++ ); - pParaPortion->GetTextPortions().Append(pNew); + rParaPortion.GetTextPortions().Append(pNew); } - DBG_ASSERT( pParaPortion->GetTextPortions().Count(), "No Portions?!" ); + DBG_ASSERT(rParaPortion.GetTextPortions().Count(), "No Portions?!"); #if OSL_DEBUG_LEVEL > 0 - OSL_ENSURE( ParaPortion::DbgCheckTextPortions(*pParaPortion), "Portion is broken?" ); + OSL_ENSURE( ParaPortion::DbgCheckTextPortions(rParaPortion), "Portion is broken?" ); #endif } -void ImpEditEngine::RecalcTextPortion( ParaPortion* pParaPortion, sal_Int32 nStartPos, sal_Int32 nNewChars ) +void ImpEditEngine::RecalcTextPortion(ParaPortion& rParaPortion, sal_Int32 nStartPos, sal_Int32 nNewChars) { - DBG_ASSERT( pParaPortion->GetTextPortions().Count(), "No Portions!" ); + DBG_ASSERT(rParaPortion.GetTextPortions().Count(), "No Portions!"); DBG_ASSERT( nNewChars, "RecalcTextPortion with Diff == 0" ); - ContentNode* const pNode = pParaPortion->GetNode(); + ContentNode* const pNode = rParaPortion.GetNode(); if ( nNewChars > 0 ) { // If an Attribute begins/ends at nStartPos, then a new portion starts @@ -2481,31 +2776,30 @@ void ImpEditEngine::RecalcTextPortion( ParaPortion* pParaPortion, sal_Int32 nSta { sal_Int32 nNewPortionPos = 0; if ( nStartPos ) - nNewPortionPos = SplitTextPortion( pParaPortion, nStartPos ) + 1; + nNewPortionPos = SplitTextPortion(rParaPortion, nStartPos) + 1; // A blank portion may be here, if the paragraph was empty, // or if a line was created by a hard line break. - if ( ( nNewPortionPos < pParaPortion->GetTextPortions().Count() ) && - !pParaPortion->GetTextPortions()[nNewPortionPos].GetLen() ) + if ( ( nNewPortionPos < rParaPortion.GetTextPortions().Count() ) && + !rParaPortion.GetTextPortions()[nNewPortionPos].GetLen() ) { - TextPortion& rTP = pParaPortion->GetTextPortions()[nNewPortionPos]; + TextPortion& rTP = rParaPortion.GetTextPortions()[nNewPortionPos]; DBG_ASSERT( rTP.GetKind() == PortionKind::TEXT, "the empty portion was no TextPortion!" ); rTP.SetLen( rTP.GetLen() + nNewChars ); } else { TextPortion* pNewPortion = new TextPortion( nNewChars ); - pParaPortion->GetTextPortions().Insert(nNewPortionPos, pNewPortion); + rParaPortion.GetTextPortions().Insert(nNewPortionPos, pNewPortion); } } else { sal_Int32 nPortionStart; - const sal_Int32 nTP = pParaPortion->GetTextPortions(). - FindPortion( nStartPos, nPortionStart ); - TextPortion& rTP = pParaPortion->GetTextPortions()[ nTP ]; + const sal_Int32 nTP = rParaPortion.GetTextPortions().FindPortion( nStartPos, nPortionStart ); + TextPortion& rTP = rParaPortion.GetTextPortions()[ nTP ]; rTP.SetLen( rTP.GetLen() + nNewChars ); - rTP.GetSize().setWidth( -1 ); + rTP.setWidth(-1); } } else @@ -2520,11 +2814,11 @@ void ImpEditEngine::RecalcTextPortion( ParaPortion* pParaPortion, sal_Int32 nSta sal_Int32 nPortion = 0; sal_Int32 nPos = 0; sal_Int32 nEnd = nStartPos-nNewChars; - sal_Int32 nPortions = pParaPortion->GetTextPortions().Count(); + sal_Int32 nPortions = rParaPortion.GetTextPortions().Count(); TextPortion* pTP = nullptr; for ( nPortion = 0; nPortion < nPortions; nPortion++ ) { - pTP = &pParaPortion->GetTextPortions()[ nPortion ]; + pTP = &rParaPortion.GetTextPortions()[ nPortion ]; if ( ( nPos+pTP->GetLen() ) > nStartPos ) { DBG_ASSERT( nPos <= nStartPos, "Wrong Start!" ); @@ -2538,14 +2832,14 @@ void ImpEditEngine::RecalcTextPortion( ParaPortion* pParaPortion, sal_Int32 nSta { // Remove portion; PortionKind nType = pTP->GetKind(); - pParaPortion->GetTextPortions().Remove( nPortion ); + rParaPortion.GetTextPortions().Remove( nPortion ); if ( nType == PortionKind::LINEBREAK ) { - TextPortion& rNext = pParaPortion->GetTextPortions()[ nPortion ]; + TextPortion& rNext = rParaPortion.GetTextPortions()[ nPortion ]; if ( !rNext.GetLen() ) { // Remove dummy portion - pParaPortion->GetTextPortions().Remove( nPortion ); + rParaPortion.GetTextPortions().Remove( nPortion ); } } } @@ -2555,48 +2849,47 @@ void ImpEditEngine::RecalcTextPortion( ParaPortion* pParaPortion, sal_Int32 nSta pTP->SetLen( pTP->GetLen() + nNewChars ); } - sal_Int32 nPortionCount = pParaPortion->GetTextPortions().Count(); + sal_Int32 nPortionCount = rParaPortion.GetTextPortions().Count(); assert( nPortionCount ); if (nPortionCount) { // No HYPHENATOR portion is allowed to get stuck right at the end... sal_Int32 nLastPortion = nPortionCount - 1; - pTP = &pParaPortion->GetTextPortions()[nLastPortion]; + pTP = &rParaPortion.GetTextPortions()[nLastPortion]; if ( pTP->GetKind() == PortionKind::HYPHENATOR ) { // Discard portion; if possible, correct the ones before, // if the Hyphenator portion has swallowed one character... if ( nLastPortion && pTP->GetLen() ) { - TextPortion& rPrev = pParaPortion->GetTextPortions()[nLastPortion - 1]; + TextPortion& rPrev = rParaPortion.GetTextPortions()[nLastPortion - 1]; DBG_ASSERT( rPrev.GetKind() == PortionKind::TEXT, "Portion?!" ); rPrev.SetLen( rPrev.GetLen() + pTP->GetLen() ); - rPrev.GetSize().setWidth( -1 ); + rPrev.setWidth(-1); } - pParaPortion->GetTextPortions().Remove( nLastPortion ); + rParaPortion.GetTextPortions().Remove( nLastPortion ); } } } #if OSL_DEBUG_LEVEL > 0 - OSL_ENSURE( ParaPortion::DbgCheckTextPortions(*pParaPortion), "Portions are broken?" ); + OSL_ENSURE( ParaPortion::DbgCheckTextPortions(rParaPortion), "Portions are broken?" ); #endif } void ImpEditEngine::SetTextRanger( std::unique_ptr<TextRanger> pRanger ) { - pTextRanger = std::move(pRanger); + mpTextRanger = std::move(pRanger); - for ( sal_Int32 nPara = 0; nPara < GetParaPortions().Count(); nPara++ ) + for (auto& pParaPortion : GetParaPortions()) { - ParaPortion& rParaPortion = GetParaPortions()[nPara]; - rParaPortion.MarkSelectionInvalid( 0 ); - rParaPortion.GetLines().Reset(); + pParaPortion->MarkSelectionInvalid( 0 ); + pParaPortion->GetLines().Reset(); } FormatFullDoc(); UpdateViews( GetActiveView() ); if ( IsUpdateLayout() && GetActiveView() ) - pActiveView->ShowCursor(false, false); + mpActiveView->ShowCursor(false, false); } void ImpEditEngine::SetVertical( bool bVertical) @@ -2604,7 +2897,7 @@ void ImpEditEngine::SetVertical( bool bVertical) if ( IsEffectivelyVertical() != bVertical) { GetEditDoc().SetVertical(bVertical); - bool bUseCharAttribs = bool(aStatus.GetControlWord() & EEControlBits::USECHARATTRIBS); + bool bUseCharAttribs = bool(maStatus.GetControlWord() & EEControlBits::USECHARATTRIBS); GetEditDoc().CreateDefFont( bUseCharAttribs ); if ( IsFormatted() ) { @@ -2616,8 +2909,10 @@ void ImpEditEngine::SetVertical( bool bVertical) void ImpEditEngine::SetRotation(TextRotation nRotation) { + if (GetEditDoc().GetRotation() == nRotation) + return; // not modified GetEditDoc().SetRotation(nRotation); - bool bUseCharAttribs = bool(aStatus.GetControlWord() & EEControlBits::USECHARATTRIBS); + bool bUseCharAttribs = bool(maStatus.GetControlWord() & EEControlBits::USECHARATTRIBS); GetEditDoc().CreateDefFont( bUseCharAttribs ); if ( IsFormatted() ) { @@ -2628,8 +2923,14 @@ void ImpEditEngine::SetRotation(TextRotation nRotation) void ImpEditEngine::SetTextColumns(sal_Int16 nColumns, sal_Int32 nSpacing) { + assert(nColumns >= 1); if (mnColumns != nColumns || mnColumnSpacing != nSpacing) { + if (nColumns == 0) + { + SAL_WARN("editeng", "bad nColumns value, ignoring"); + nColumns = 1; + } mnColumns = nColumns; mnColumnSpacing = nSpacing; if (IsFormatted()) @@ -2710,7 +3011,7 @@ void ImpEditEngine::SeekCursor( ContentNode* pNode, sal_Int32 nPos, SvxFont& rFo /* * Scan through char attributes of pNode */ - if ( aStatus.UseCharAttribs() ) + if (maStatus.UseCharAttribs()) { CharAttribList::AttribsType& rAttribs = pNode->GetCharAttribs().GetAttribs(); size_t nAttr = 0; @@ -2732,7 +3033,11 @@ void ImpEditEngine::SeekCursor( ContentNode* pNode, sal_Int32 nPos, SvxFont& rFo // #i1550# hard color attrib should win over text color from field if ( pAttrib->Which() == EE_FEATURE_FIELD ) { - EditCharAttrib* pColorAttr = pNode->GetCharAttribs().FindAttrib( EE_CHAR_COLOR, nPos ); + // These Attribs positions come from PaMs, so their interval is right-open and left-closed + // when SeekCursor is called, nPos is incremented by 1. I do not know why... + // probably designed to be a nEndPos, and like in a PaM, it is the position after the actual character. + sal_Int32 nPosActual = nPos > 0 ? nPos - 1 : 0; + EditCharAttrib* pColorAttr = pNode->GetCharAttribs().FindAttribRightOpen( EE_CHAR_COLOR, nPosActual ); if ( pColorAttr ) pColorAttr->SetFont( rFont, pOut ); } @@ -2754,12 +3059,12 @@ void ImpEditEngine::SeekCursor( ContentNode* pNode, sal_Int32 nPos, SvxFont& rFo if ( (rFont.GetKerning() != FontKerning::NONE) && IsKernAsianPunctuation() && ( nScriptTypeI18N == i18n::ScriptType::ASIAN ) ) rFont.SetKerning( rFont.GetKerning() | FontKerning::Asian ); - if ( aStatus.DoNotUseColors() ) + if (maStatus.DoNotUseColors()) { rFont.SetColor( /* rColorItem.GetValue() */ COL_BLACK ); } - if ( aStatus.DoStretch() || ( nRelWidth != 100 ) ) + if (maStatus.DoStretch() || ( nRelWidth != 100 )) { // For the current Output device, because otherwise if RefDev=Printer its looks // ugly on the screen! @@ -2778,24 +3083,29 @@ void ImpEditEngine::SeekCursor( ContentNode* pNode, sal_Int32 nPos, SvxFont& rFo Size aRealSz( aMetric.GetFontSize() ); rFont.SetPropr( 100 ); - if ( aStatus.DoStretch() ) + if (maStatus.DoStretch()) { - if ( nStretchY != 100 ) + if (maScalingParameters.fFontY != 1.0) { - aRealSz.setHeight( aRealSz.Height() * nStretchY ); - aRealSz.setHeight( aRealSz.Height() / 100 ); + double fHeightRounded = roundToNearestPt(aRealSz.Height()); + double fNewHeight = fHeightRounded * maScalingParameters.fFontY; + fNewHeight = roundToNearestPt(fNewHeight); + aRealSz.setHeight(basegfx::fround<tools::Long>(fNewHeight)); } - if ( nStretchX != 100 ) + if (maScalingParameters.fFontX != 1.0) { - if ( nStretchX == nStretchY && - nRelWidth == 100 ) + auto fFontX = maScalingParameters.fFontX; + auto fFontY = maScalingParameters.fFontY; + if (fFontX == fFontY && nRelWidth == 100 ) { aRealSz.setWidth( 0 ); } else { - aRealSz.setWidth( aRealSz.Width() * nStretchX ); - aRealSz.setWidth( aRealSz.Width() / 100 ); + double fWidthRounded = roundToNearestPt(aRealSz.Width()); + double fNewWidth = fWidthRounded * fFontX; + fNewWidth = roundToNearestPt(fNewWidth); + aRealSz.setWidth(basegfx::fround<tools::Long>(fNewWidth)); // Also the Kerning: (long due to handle Interim results) tools::Long nKerning = rFont.GetFixKerning(); @@ -2810,17 +3120,15 @@ void ImpEditEngine::SeekCursor( ContentNode* pNode, sal_Int32 nPos, SvxFont& rFo >0 >100 > (Proportional) <0 >100 < (The amount, thus disproportional) */ - if ( ( nKerning < 0 ) && ( nStretchX > 100 ) ) + if (nKerning < 0 && fFontX > 1.0) { // disproportional - nKerning *= 100; - nKerning /= nStretchX; + nKerning = basegfx::fround(nKerning / fFontX); } else if ( nKerning ) { // Proportional - nKerning *= nStretchX; - nKerning /= 100; + nKerning = basegfx::fround(nKerning * fFontX); } rFont.SetFixKerning( static_cast<short>(nKerning) ); } @@ -2862,6 +3170,8 @@ void ImpEditEngine::SeekCursor( ContentNode* pNode, sal_Int32 nPos, SvxFont& rFo ExtTextInputAttr nAttr = mpIMEInfos->pAttribs[ nPos - mpIMEInfos->aPos.GetIndex() - 1 ]; if ( nAttr & ExtTextInputAttr::Underline ) rFont.SetUnderline( LINESTYLE_SINGLE ); + else if ( nAttr & ExtTextInputAttr::DoubleUnderline ) + rFont.SetUnderline( LINESTYLE_DOUBLE ); else if ( nAttr & ExtTextInputAttr::BoldUnderline ) rFont.SetUnderline( LINESTYLE_BOLD ); else if ( nAttr & ExtTextInputAttr::DottedUnderline ) @@ -2895,11 +3205,11 @@ void ImpEditEngine::RecalcFormatterFontMetrics( FormatterFontMetric& rCurMetrics if ( nPropr != 100 ) { rFont.SetPropr( 100 ); - rFont.SetPhysFont(*pRefDev); + rFont.SetPhysFont(*mpRefDev); } sal_uInt16 nAscent, nDescent; - FontMetric aMetric( pRefDev->GetFontMetric() ); + FontMetric aMetric(mpRefDev->GetFontMetric()); nAscent = static_cast<sal_uInt16>(aMetric.GetAscent()); if ( IsAddExtLeading() ) nAscent = sal::static_int_cast< sal_uInt16 >( @@ -2915,10 +3225,10 @@ void ImpEditEngine::RecalcFormatterFontMetrics( FormatterFontMetric& rCurMetrics { sal_uInt16 nIntLeading = ( aMetric.GetInternalLeading() > 0 ) ? static_cast<sal_uInt16>(aMetric.GetInternalLeading()) : 0; // Fonts without leading cause problems - if ( ( nIntLeading == 0 ) && ( pRefDev->GetOutDevType() == OUTDEV_PRINTER ) ) + if ( ( nIntLeading == 0 ) && (mpRefDev->GetOutDevType() == OUTDEV_PRINTER)) { // Lets see what Leading one gets on the screen - VclPtr<VirtualDevice> pVDev = GetVirtualDevice( pRefDev->GetMapMode(), pRefDev->GetDrawMode() ); + VclPtr<VirtualDevice> pVDev = GetVirtualDevice(mpRefDev->GetMapMode(), mpRefDev->GetDrawMode()); rFont.SetPhysFont(*pVDev); aMetric = pVDev->GetFontMetric(); @@ -3055,13 +3365,13 @@ Point ImpEditEngine::MoveToNextLine( // Move the point by the requested distance in Y direction adjustYDirectionAware(rMovePos, nLineHeight); // Check if the resulting position has moved beyond the limits, and more columns left. - // The limits are defined by a rectangle starting from aOrigin with width of aPaperSize - // and height of nCurTextHeight + // The limits are defined by a rectangle starting from aOrigin with width of maPaperSize + // and height of mnCurTextHeight Point aOtherCorner = aOrigin; - adjustXDirectionAware(aOtherCorner, getWidthDirectionAware(aPaperSize)); - adjustYDirectionAware(aOtherCorner, nCurTextHeight); + adjustXDirectionAware(aOtherCorner, getWidthDirectionAware(maPaperSize)); + adjustYDirectionAware(aOtherCorner, mnCurTextHeight); tools::Long nNeeded - = getYOverflowDirectionAware(rMovePos, tools::Rectangle::Justify(aOrigin, aOtherCorner)); + = getYOverflowDirectionAware(rMovePos, tools::Rectangle::Normalize(aOrigin, aOtherCorner)); if (pnHeightNeededToNotWrap) *pnHeightNeededToNotWrap = nNeeded; if (nNeeded && rColumn < mnColumns) @@ -3076,7 +3386,7 @@ Point ImpEditEngine::MoveToNextLine( // Move the point by the requested distance in Y direction adjustYDirectionAware(rMovePos, nLineHeight); // Move the point by the column+spacing distance in X direction - adjustXDirectionAware(rMovePos, GetColumnWidth(aPaperSize) + mnColumnSpacing); + adjustXDirectionAware(rMovePos, GetColumnWidth(maPaperSize) + mnColumnSpacing); } } @@ -3084,7 +3394,6 @@ Point ImpEditEngine::MoveToNextLine( } // TODO: use IterateLineAreas in ImpEditEngine::Paint, to avoid algorithm duplication - void ImpEditEngine::Paint( OutputDevice& rOutDev, tools::Rectangle aClipRect, Point aStartPos, bool bStripOnly, Degree10 nOrientation ) { if ( !IsUpdateLayout() && !bStripOnly ) @@ -3097,7 +3406,7 @@ void ImpEditEngine::Paint( OutputDevice& rOutDev, tools::Rectangle aClipRect, Po tools::Long nFirstVisYPos = - rOutDev.GetMapMode().GetOrigin().Y(); DBG_ASSERT( GetParaPortions().Count(), "No ParaPortion?!" ); - SvxFont aTmpFont( GetParaPortions()[0].GetNode()->GetCharAttribs().GetDefFont() ); + SvxFont aTmpFont = GetParaPortions().getRef(0).GetNode()->GetCharAttribs().GetDefFont(); vcl::PDFExtOutDevData* const pPDFExtOutDevData = dynamic_cast< vcl::PDFExtOutDevData* >( rOutDev.GetExtOutDevData() ); // In the case of rotated text is aStartPos considered TopLeft because @@ -3105,13 +3414,6 @@ void ImpEditEngine::Paint( OutputDevice& rOutDev, tools::Rectangle aClipRect, Po // un-scrolled. // The rectangle is infinite. const Point aOrigin( aStartPos ); - double nCos = 0.0, nSin = 0.0; - if ( nOrientation ) - { - double nRealOrientation = nOrientation.get()*F_PI1800; - nCos = cos( nRealOrientation ); - nSin = sin( nRealOrientation ); - } // #110496# Added some more optional metafile comments. This // change: factored out some duplicated code. @@ -3124,19 +3426,19 @@ void ImpEditEngine::Paint( OutputDevice& rOutDev, tools::Rectangle aClipRect, Po // Over all the paragraphs... - for ( sal_Int32 n = 0; n < GetParaPortions().Count(); n++ ) + for (sal_Int32 nParaPortion = 0; nParaPortion < GetParaPortions().Count(); nParaPortion++) { - const ParaPortion& rPortion = GetParaPortions()[n]; + ParaPortion const& rParaPortion = GetParaPortions().getRef(nParaPortion); // if when typing idle formatting, asynchronous Paint. // Invisible Portions may be invalid. - if ( rPortion.IsVisible() && rPortion.IsInvalid() ) + if (rParaPortion.IsVisible() && rParaPortion.IsInvalid()) return; if ( pPDFExtOutDevData ) - pPDFExtOutDevData->BeginStructureElement( vcl::PDFWriter::Paragraph ); + pPDFExtOutDevData->WrapBeginStructureElement(vcl::PDFWriter::Paragraph); - const tools::Long nParaHeight = rPortion.GetHeight(); - if ( rPortion.IsVisible() && ( + const tools::Long nParaHeight = rParaPortion.GetHeight(); + if (rParaPortion.IsVisible() && ( ( !IsEffectivelyVertical() && ( ( aStartPos.Y() + nParaHeight ) > aClipRect.Top() ) ) || ( IsEffectivelyVertical() && IsTopToBottom() && ( ( aStartPos.X() - nParaHeight ) < aClipRect.Right() ) ) || ( IsEffectivelyVertical() && !IsTopToBottom() && ( ( aStartPos.X() + nParaHeight ) > aClipRect.Left() ) ) ) ) @@ -3146,22 +3448,21 @@ void ImpEditEngine::Paint( OutputDevice& rOutDev, tools::Rectangle aClipRect, Po // Over the lines of the paragraph... - const sal_Int32 nLines = rPortion.GetLines().Count(); + const sal_Int32 nLines = rParaPortion.GetLines().Count(); const sal_Int32 nLastLine = nLines-1; bool bEndOfParagraphWritten(false); - adjustYDirectionAware(aStartPos, rPortion.GetFirstLineOffset()); + adjustYDirectionAware(aStartPos, rParaPortion.GetFirstLineOffset()); - const SvxLineSpacingItem& rLSItem = rPortion.GetNode()->GetContentAttribs().GetItem( EE_PARA_SBL ); + const SvxLineSpacingItem& rLSItem = rParaPortion.GetNode()->GetContentAttribs().GetItem( EE_PARA_SBL ); sal_uInt16 nSBL = ( rLSItem.GetInterLineSpaceRule() == SvxInterLineSpaceRule::Fix ) - ? GetYValue( rLSItem.GetInterLineSpace() ) : 0; + ? scaleYSpacingValue(rLSItem.GetInterLineSpace()) : 0; bool bPaintBullet (false); for ( sal_Int32 nLine = 0; nLine < nLines; nLine++ ) { - const EditLine* const pLine = &rPortion.GetLines()[nLine]; - assert( pLine && "NULL-Pointer in the line iterator in UpdateViews" ); + EditLine* pLine = &GetParaPortions().getRef(nParaPortion).GetLines()[nLine]; sal_Int32 nIndex = pLine->GetStart(); tools::Long nLineHeight = pLine->GetHeight(); if (nLine != nLastLine) @@ -3185,10 +3486,10 @@ void ImpEditEngine::Paint( OutputDevice& rOutDev, tools::Rectangle aClipRect, Po { Point aLineStart(aStartPos); adjustYDirectionAware(aLineStart, -nLineHeight); - GetEditEnginePtr()->PaintingFirstLine(n, aLineStart, aOrigin, nOrientation, rOutDev); + GetEditEnginePtr()->PaintingFirstLine(nParaPortion, aLineStart, aOrigin, nOrientation, rOutDev); // Remember whether a bullet was painted. - const SfxBoolItem& rBulletState = pEditEngine->GetParaAttrib(n, EE_PARA_BULLETSTATE); + const SfxBoolItem& rBulletState = mpEditEngine->GetParaAttrib(nParaPortion, EE_PARA_BULLETSTATE); bPaintBullet = rBulletState.GetValue(); } @@ -3197,15 +3498,24 @@ void ImpEditEngine::Paint( OutputDevice& rOutDev, tools::Rectangle aClipRect, Po bool bParsingFields = false; std::vector< sal_Int32 >::iterator itSubLines; + tools::Long nFirstPortionXOffset = 0; for ( sal_Int32 nPortion = pLine->GetStartPortion(); nPortion <= pLine->GetEndPortion(); nPortion++ ) { - DBG_ASSERT( rPortion.GetTextPortions().Count(), "Line without Textportion in Paint!" ); - const TextPortion& rTextPortion = rPortion.GetTextPortions()[nPortion]; + DBG_ASSERT(rParaPortion.GetTextPortions().Count(), "Line without Textportion in Paint!"); + const TextPortion& rTextPortion = rParaPortion.GetTextPortions()[nPortion]; - const tools::Long nPortionXOffset = GetPortionXOffset( &rPortion, pLine, nPortion ); + const tools::Long nPortionXOffset = GetPortionXOffset(rParaPortion, *pLine, nPortion); setXDirectionAwareFrom(aTmpPos, aStartPos); - adjustXDirectionAware(aTmpPos, nPortionXOffset); + + if (nPortion == pLine->GetStartPortion()) + nFirstPortionXOffset = nPortionXOffset; + + if (!bParsingFields) + adjustXDirectionAware(aTmpPos, nPortionXOffset); + else + adjustXDirectionAware(aTmpPos, nFirstPortionXOffset); + if (isXOverflowDirectionAware(aTmpPos, aClipRect)) break; // No further output in line necessary @@ -3215,7 +3525,7 @@ void ImpEditEngine::Paint( OutputDevice& rOutDev, tools::Rectangle aClipRect, Po case PortionKind::FIELD: case PortionKind::HYPHENATOR: { - SeekCursor( rPortion.GetNode(), nIndex+1, aTmpFont, &rOutDev ); + SeekCursor(rParaPortion.GetNode(), nIndex + 1, aTmpFont, &rOutDev); bool bDrawFrame = false; @@ -3241,7 +3551,7 @@ void ImpEditEngine::Paint( OutputDevice& rOutDev, tools::Rectangle aClipRect, Po aTmpFont.SetFillColor( COL_LIGHTGRAY ); aTmpFont.SetTransparent( sal_False ); } - else if ( GetI18NScriptType( EditPaM( rPortion.GetNode(), nIndex+1 ) ) == i18n::ScriptType::COMPLEX ) + else if (GetI18NScriptType(EditPaM(rParaPortion.GetNode(), nIndex + 1)) == i18n::ScriptType::COMPLEX) { aTmpFont.SetFillColor( COL_LIGHTCYAN ); aTmpFont.SetTransparent( sal_False ); @@ -3252,21 +3562,29 @@ void ImpEditEngine::Paint( OutputDevice& rOutDev, tools::Rectangle aClipRect, Po // #114278# Saving both layout mode and language (since I'm // potentially changing both) rOutDev.Push( vcl::PushFlags::TEXTLAYOUTMODE|vcl::PushFlags::TEXTLANGUAGE ); - ImplInitLayoutMode(rOutDev, n, nIndex); + ImplInitLayoutMode(rOutDev, nParaPortion, nIndex); ImplInitDigitMode(rOutDev, aTmpFont.GetLanguage()); OUString aText; sal_Int32 nTextStart = 0; sal_Int32 nTextLen = 0; - const tools::Long* pDXArray = nullptr; - std::vector<tools::Long> aTmpDXArray; + std::span<const sal_Int32> pDXArray; + std::span<const sal_Bool> pKashidaArray; + KernArray aTmpDXArray; if ( rTextPortion.GetKind() == PortionKind::TEXT ) { - aText = rPortion.GetNode()->GetString(); + aText = rParaPortion.GetNode()->GetString(); nTextStart = nIndex; nTextLen = rTextPortion.GetLen(); - pDXArray = pLine->GetCharPosArray().data() + (nIndex - pLine->GetStart()); + pDXArray = std::span(pLine->GetCharPosArray().data() + (nIndex - pLine->GetStart()), + pLine->GetCharPosArray().size() - (nIndex - pLine->GetStart())); + + if (!pLine->GetKashidaArray().empty()) + { + pKashidaArray = std::span(pLine->GetKashidaArray().data() + (nIndex - pLine->GetStart()), + pLine->GetKashidaArray().size() - (nIndex - pLine->GetStart())); + } // Paint control characters (#i55716#) /* XXX: Given that there's special handling @@ -3274,7 +3592,7 @@ void ImpEditEngine::Paint( OutputDevice& rOutDev, tools::Rectangle aClipRect, Po * (U+200B ZERO WIDTH SPACE and U+2060 WORD * JOINER) it is assumed to be not relevant * for MarkUrlFields(). */ - if ( aStatus.MarkNonUrlFields() ) + if (maStatus.MarkNonUrlFields()) { sal_Int32 nTmpIdx; const sal_Int32 nTmpEnd = nTextStart + rTextPortion.GetLen(); @@ -3287,7 +3605,8 @@ void ImpEditEngine::Paint( OutputDevice& rOutDev, tools::Rectangle aClipRect, Po if ( 0x200B == cChar || 0x2060 == cChar ) { - tools::Long nHalfBlankWidth = aTmpFont.QuickGetTextSize( &rOutDev, " ", 0, 1 ).Width() / 2; + tools::Long nHalfBlankWidth = aTmpFont.QuickGetTextSize( &rOutDev, + " ", 0, 1, nullptr ).Width() / 2; const tools::Long nAdvanceX = ( nTmpIdx == nTmpEnd ? rTextPortion.GetSize().Width() : @@ -3323,13 +3642,14 @@ void ImpEditEngine::Paint( OutputDevice& rOutDev, tools::Rectangle aClipRect, Po aTmpFont.SetPropr( 25 ); aTmpFont.SetPhysFont(rOutDev); - const Size aSlashSize = aTmpFont.QuickGetTextSize( &rOutDev, aSlash, 0, 1 ); + const Size aSlashSize = aTmpFont.QuickGetTextSize( &rOutDev, + aSlash, 0, 1, nullptr ); Point aSlashPos( aTmpPos ); const tools::Long nAddX = nHalfBlankWidth - aSlashSize.Width() / 2; setXDirectionAwareFrom(aSlashPos, aTopLeftRectPos); adjustXDirectionAware(aSlashPos, nAddX); - aTmpFont.QuickDrawText( &rOutDev, aSlashPos, aSlash, 0, 1 ); + aTmpFont.QuickDrawText( &rOutDev, aSlashPos, aSlash, 0, 1, {} ); aTmpFont.SetEscapement( nOldEscapement ); aTmpFont.SetPropr( nOldPropr ); @@ -3341,22 +3661,19 @@ void ImpEditEngine::Paint( OutputDevice& rOutDev, tools::Rectangle aClipRect, Po } else if ( rTextPortion.GetKind() == PortionKind::FIELD ) { - const EditCharAttrib* pAttr = rPortion.GetNode()->GetCharAttribs().FindFeature(nIndex); + const EditCharAttrib* pAttr = rParaPortion.GetNode()->GetCharAttribs().FindFeature(nIndex); assert( pAttr && "Field not found"); DBG_ASSERT( dynamic_cast< const SvxFieldItem* >( pAttr->GetItem() ) != nullptr, "Field of the wrong type! "); aText = static_cast<const EditCharAttribField*>(pAttr)->GetFieldValue(); nTextStart = 0; nTextLen = aText.getLength(); ExtraPortionInfo *pExtraInfo = rTextPortion.GetExtraInfos(); - // Do not split the Fields into different lines while editing - // With EditView on Overlay bStripOnly is now set for stripping to - // primitives. To stay compatible in EditMode use pActiveView to detect - // when we are in EditMode. For whatever reason URLs are drawn as single - // line in edit mode, originally clipped against edit area (which is no - // longer done in Overlay mode and allows to *read* the URL). - // It would be difficult to change this due to needed adaptations in - // EditEngine (look for lineBreaksList creation) - if( nullptr == pActiveView && bStripOnly && !bParsingFields && pExtraInfo && !pExtraInfo->lineBreaksList.empty() ) + //For historical reasons URLs was drawn as single line in edit mode + //but now we changed it, so it wraps similar as simple text. + //It is not perfect, it still use lineBreaksList, so it won’t seek + //word ends to wrap text there, but it would be difficult to change + //this due to needed adaptations in EditEngine + if (bStripOnly && !bParsingFields && pExtraInfo && !pExtraInfo->lineBreaksList.empty()) { bParsingFields = true; itSubLines = pExtraInfo->lineBreaksList.begin(); @@ -3373,6 +3690,7 @@ void ImpEditEngine::Paint( OutputDevice& rOutDev, tools::Rectangle aClipRect, Po aTmpPos += MoveToNextLine(aStartPos, nMaxAscent, nColumn, aOrigin); + adjustXDirectionAware(aTmpPos, -pLine->GetNextLinePosXDiff()); } std::vector< sal_Int32 >::iterator curIt = itSubLines; ++itSubLines; @@ -3386,12 +3704,43 @@ void ImpEditEngine::Paint( OutputDevice& rOutDev, tools::Rectangle aClipRect, Po nTextStart = *curIt; nTextLen = nTextLen - nTextStart; bParsingFields = false; + + if (nLine + 1 < nLines) + { + // tdf#148966 don't paint the line break following a + // multiline field based on a compat flag + OutlinerEditEng* pOutlEditEng{ dynamic_cast<OutlinerEditEng*>(mpEditEngine)}; + int nStartNextLine = rParaPortion.GetLines()[nLine + 1].GetStartPortion(); + const TextPortion& rNextTextPortion = rParaPortion.GetTextPortions()[nStartNextLine]; + if (pOutlEditEng + && pOutlEditEng->GetCompatFlag(SdrCompatibilityFlag::IgnoreBreakAfterMultilineField) + .value_or(false)) + { + if (rNextTextPortion.GetKind() == PortionKind::LINEBREAK) + ++nLine; //ignore the following linebreak + } + else if (mpActiveView && rNextTextPortion.GetKind() == PortionKind::LINEBREAK) + { + // if we are at edit mode, the compat flag does not work + // here we choose to work if compat flag is true, + // this is better for newer documents + nLine++; + } + if (rNextTextPortion.GetKind() != PortionKind::LINEBREAK) + { + nLine++; + pLine = &GetParaPortions().getRef(nParaPortion).GetLines()[nLine]; + } + } } } aTmpFont.SetPhysFont(*GetRefDevice()); - aTmpFont.QuickGetTextSize( GetRefDevice(), aText, nTextStart, nTextLen, &aTmpDXArray ); - pDXArray = aTmpDXArray.data(); + aTmpFont.QuickGetTextSize( GetRefDevice(), aText, nTextStart, nTextLen, + &aTmpDXArray ); + assert(aTmpDXArray.get_factor() == 1); + std::vector<sal_Int32>& rKernArray = aTmpDXArray.get_subunit_array(); + pDXArray = rKernArray; // add a meta file comment if we record to a metafile if( bMetafileValid ) @@ -3416,8 +3765,11 @@ void ImpEditEngine::Paint( OutputDevice& rOutDev, tools::Rectangle aClipRect, Po // crash when accessing 0 pointer in pDXArray aTmpFont.SetPhysFont(*GetRefDevice()); - aTmpFont.QuickGetTextSize( GetRefDevice(), aText, 0, aText.getLength(), &aTmpDXArray ); - pDXArray = aTmpDXArray.data(); + aTmpFont.QuickGetTextSize( GetRefDevice(), aText, 0, aText.getLength(), + &aTmpDXArray ); + assert(aTmpDXArray.get_factor() == 1); + std::vector<sal_Int32>& rKernArray = aTmpDXArray.get_subunit_array(); + pDXArray = rKernArray; } tools::Long nTxtWidth = rTextPortion.GetSize().Width(); @@ -3435,7 +3787,7 @@ void ImpEditEngine::Paint( OutputDevice& rOutDev, tools::Rectangle aClipRect, Po if(GetStatus().DoOnlineSpelling() && rTextPortion.GetLen()) { - WrongList* pWrongs = rPortion.GetNode()->GetWrongList(); + WrongList* pWrongs = rParaPortion.GetNode()->GetWrongList(); if(pWrongs && !pWrongs->empty()) { @@ -3482,7 +3834,7 @@ void ImpEditEngine::Paint( OutputDevice& rOutDev, tools::Rectangle aClipRect, Po if(PortionKind::FIELD == rTextPortion.GetKind()) { - const EditCharAttrib* pAttr = rPortion.GetNode()->GetCharAttribs().FindFeature(nIndex); + const EditCharAttrib* pAttr = rParaPortion.GetNode()->GetCharAttribs().FindFeature(nIndex); const SvxFieldItem* pFieldItem = dynamic_cast<const SvxFieldItem*>(pAttr->GetItem()); if(pFieldItem) @@ -3494,7 +3846,7 @@ void ImpEditEngine::Paint( OutputDevice& rOutDev, tools::Rectangle aClipRect, Po // support for EOC, EOW, EOS TEXT comments. To support that, // the locale is needed. With the locale and a XBreakIterator it is // possible to re-create the text marking info on primitive level - const lang::Locale aLocale(GetLocale(EditPaM(rPortion.GetNode(), nIndex + 1))); + const lang::Locale aLocale(GetLocale(EditPaM(rParaPortion.GetNode(), nIndex + 1))); // create EOL and EOP bools const bool bEndOfLine(nPortion == pLine->GetEndPortion()); @@ -3513,8 +3865,8 @@ void ImpEditEngine::Paint( OutputDevice& rOutDev, tools::Rectangle aClipRect, Po ImplCalcDigitLang(aTmpFont.GetLanguage())); // StripPortions() data callback - GetEditEnginePtr()->DrawingText( aOutPos, aText, nTextStart, nTextLen, pDXArray, - aTmpFont, n, rTextPortion.GetRightToLeftLevel(), + GetEditEnginePtr()->DrawingText( aOutPos, aText, nTextStart, nTextLen, pDXArray, pKashidaArray, + aTmpFont, nParaPortion, rTextPortion.GetRightToLeftLevel(), !aWrongSpellVector.empty() ? &aWrongSpellVector : nullptr, pFieldData, bEndOfLine, bEndOfParagraph, // support for EOL/EOP TEXT comments @@ -3542,7 +3894,7 @@ void ImpEditEngine::Paint( OutputDevice& rOutDev, tools::Rectangle aClipRect, Po aTmpFont.SetEscapement( 0 ); } - aOutPos = lcl_ImplCalcRotatedPos( aOutPos, aOrigin, nSin, nCos ); + aOrigin.RotateAround(aOutPos, nOrientation); aTmpFont.SetOrientation( aTmpFont.GetOrientation()+nOrientation ); aTmpFont.SetPhysFont(rOutDev); @@ -3561,20 +3913,20 @@ void ImpEditEngine::Paint( OutputDevice& rOutDev, tools::Rectangle aClipRect, Po // base line of the original font height... // But only if there was something underlined before! bool bSpecialUnderline = false; - EditCharAttrib* pPrev = rPortion.GetNode()->GetCharAttribs().FindAttrib( EE_CHAR_ESCAPEMENT, nIndex ); + EditCharAttrib* pPrev = rParaPortion.GetNode()->GetCharAttribs().FindAttrib( EE_CHAR_ESCAPEMENT, nIndex ); if ( pPrev ) { SvxFont aDummy; // Underscore in front? if ( pPrev->GetStart() ) { - SeekCursor( rPortion.GetNode(), pPrev->GetStart(), aDummy ); + SeekCursor( rParaPortion.GetNode(), pPrev->GetStart(), aDummy ); if ( aDummy.GetUnderline() != LINESTYLE_NONE ) bSpecialUnderline = true; } - if ( !bSpecialUnderline && ( pPrev->GetEnd() < rPortion.GetNode()->Len() ) ) + if ( !bSpecialUnderline && ( pPrev->GetEnd() < rParaPortion.GetNode()->Len() ) ) { - SeekCursor( rPortion.GetNode(), pPrev->GetEnd()+1, aDummy ); + SeekCursor( rParaPortion.GetNode(), pPrev->GetEnd()+1, aDummy ); if ( aDummy.GetUnderline() != LINESTYLE_NONE ) bSpecialUnderline = true; } @@ -3586,11 +3938,14 @@ void ImpEditEngine::Paint( OutputDevice& rOutDev, tools::Rectangle aClipRect, Po aTmpFont.SetEscapement( 0 ); aTmpFont.SetPropr( 100 ); aTmpFont.SetPhysFont(rOutDev); - OUStringBuffer aBlanks; + OUStringBuffer aBlanks(nTextLen); comphelper::string::padToLength( aBlanks, nTextLen, ' ' ); Point aUnderlinePos( aOutPos ); if ( nOrientation ) - aUnderlinePos = lcl_ImplCalcRotatedPos( aTmpPos, aOrigin, nSin, nCos ); + { + aUnderlinePos = aTmpPos; + aOrigin.RotateAround(aUnderlinePos, nOrientation); + } rOutDev.DrawStretchText( aUnderlinePos, aSz.Width(), aBlanks.makeStringAndClear(), 0, nTextLen ); aTmpFont.SetUnderline( LINESTYLE_NONE ); @@ -3616,47 +3971,63 @@ void ImpEditEngine::Paint( OutputDevice& rOutDev, tools::Rectangle aClipRect, Po ' ' == aText[nTextStart + nTextLen - 1] ) --nTextLen; + // PDF export: + const SvxFieldData* pFieldData = nullptr; + if (pPDFExtOutDevData) + { + if (rTextPortion.GetKind() == PortionKind::FIELD) + { + const EditCharAttrib* pAttr = rParaPortion.GetNode()->GetCharAttribs().FindFeature(nIndex); + const SvxFieldItem* pFieldItem = dynamic_cast<const SvxFieldItem*>(pAttr->GetItem()); + if (pFieldItem) + { + pFieldData = pFieldItem->GetField(); + auto pUrlField = dynamic_cast<const SvxURLField*>(pFieldData); + if (pUrlField) + if (pPDFExtOutDevData->GetIsExportTaggedPDF()) + pPDFExtOutDevData->WrapBeginStructureElement(vcl::PDFWriter::Link, "Link"); + } + } + } + // output directly - aTmpFont.QuickDrawText( &rOutDev, aRealOutPos, aText, nTextStart, nTextLen, pDXArray ); + aTmpFont.QuickDrawText( &rOutDev, aRealOutPos, aText, nTextStart, nTextLen, pDXArray, pKashidaArray ); if ( bDrawFrame ) { Point aTopLeft( aTmpPos ); aTopLeft.AdjustY( -(pLine->GetMaxAscent()) ); if ( nOrientation ) - aTopLeft = lcl_ImplCalcRotatedPos( aTopLeft, aOrigin, nSin, nCos ); + aOrigin.RotateAround(aTopLeft, nOrientation); tools::Rectangle aRect( aTopLeft, rTextPortion.GetSize() ); rOutDev.DrawRect( aRect ); } // PDF export: - if ( pPDFExtOutDevData ) + if (pPDFExtOutDevData) { - if ( rTextPortion.GetKind() == PortionKind::FIELD ) + if (auto pUrlField = dynamic_cast<const SvxURLField*>(pFieldData)) { - const EditCharAttrib* pAttr = rPortion.GetNode()->GetCharAttribs().FindFeature(nIndex); - const SvxFieldItem* pFieldItem = dynamic_cast<const SvxFieldItem*>(pAttr->GetItem()); - if( pFieldItem ) + Point aTopLeft(aTmpPos); + aTopLeft.AdjustY(-(pLine->GetMaxAscent())); + + tools::Rectangle aRect(aTopLeft, rTextPortion.GetSize()); + vcl::PDFExtOutDevBookmarkEntry aBookmark; + aBookmark.nLinkId = pPDFExtOutDevData->CreateLink(aRect, pUrlField->GetRepresentation()); + aBookmark.aBookmark = pUrlField->GetURL(); + std::vector< vcl::PDFExtOutDevBookmarkEntry >& rBookmarks = pPDFExtOutDevData->GetBookmarks(); + rBookmarks.push_back(aBookmark); + + if (pPDFExtOutDevData->GetIsExportTaggedPDF()) { - const SvxFieldData* pFieldData = pFieldItem->GetField(); - if ( auto pUrlField = dynamic_cast< const SvxURLField* >( pFieldData ) ) - { - Point aTopLeft( aTmpPos ); - aTopLeft.AdjustY( -(pLine->GetMaxAscent()) ); - - tools::Rectangle aRect( aTopLeft, rTextPortion.GetSize() ); - vcl::PDFExtOutDevBookmarkEntry aBookmark; - aBookmark.nLinkId = pPDFExtOutDevData->CreateLink( aRect ); - aBookmark.aBookmark = pUrlField->GetURL(); - std::vector< vcl::PDFExtOutDevBookmarkEntry >& rBookmarks = pPDFExtOutDevData->GetBookmarks(); - rBookmarks.push_back( aBookmark ); - } + pPDFExtOutDevData->SetStructureAttributeNumerical(vcl::PDFWriter::LinkAnnotation, aBookmark.nLinkId); + pPDFExtOutDevData->EndStructureElement(); } } } } - const WrongList* const pWrongList = rPortion.GetNode()->GetWrongList(); + const WrongList* const pWrongList = rParaPortion.GetNode()->GetWrongList(); if ( GetStatus().DoOnlineSpelling() && pWrongList && !pWrongList->empty() && rTextPortion.GetLen() ) { {//#105750# adjust LinePos for superscript or subscript text @@ -3669,7 +4040,10 @@ void ImpEditEngine::Paint( OutputDevice& rOutDev, tools::Rectangle aClipRect, Po } Color aOldColor( rOutDev.GetLineColor() ); rOutDev.SetLineColor( GetColorConfig().GetColorValue( svtools::SPELL ).nColor ); - 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, IsEffectivelyVertical(), rTextPortion.IsRightToLeft() ); + lcl_DrawRedLines(rOutDev, aTmpFont.GetFontSize().Height(), aRedLineTmpPos, + static_cast<size_t>(nIndex), static_cast<size_t>(nIndex) + rTextPortion.GetLen(), + pDXArray, rParaPortion.GetNode()->GetWrongList(), nOrientation, + aOrigin, IsEffectivelyVertical(), rTextPortion.IsRightToLeft()); rOutDev.SetLineColor( aOldColor ); } } @@ -3681,7 +4055,7 @@ void ImpEditEngine::Paint( OutputDevice& rOutDev, tools::Rectangle aClipRect, Po // add a meta file comment if we record to a metafile if( bMetafileValid ) { - const EditCharAttrib* pAttr = rPortion.GetNode()->GetCharAttribs().FindFeature(nIndex); + const EditCharAttrib* pAttr = rParaPortion.GetNode()->GetCharAttribs().FindFeature(nIndex); assert( pAttr && "Field not found" ); const SvxFieldItem* pFieldItem = dynamic_cast<const SvxFieldItem*>(pAttr->GetItem()); @@ -3703,12 +4077,12 @@ void ImpEditEngine::Paint( OutputDevice& rOutDev, tools::Rectangle aClipRect, Po { if ( rTextPortion.GetExtraValue() && ( rTextPortion.GetExtraValue() != ' ' ) ) { - SeekCursor( rPortion.GetNode(), nIndex+1, aTmpFont, &rOutDev ); + SeekCursor(rParaPortion.GetNode(), nIndex+1, aTmpFont, &rOutDev); aTmpFont.SetTransparent( false ); aTmpFont.SetEscapement( 0 ); aTmpFont.SetPhysFont(rOutDev); tools::Long nCharWidth = aTmpFont.QuickGetTextSize( &rOutDev, - OUString(rTextPortion.GetExtraValue()), 0, 1 ).Width(); + OUString(rTextPortion.GetExtraValue()), 0, 1, {} ).Width(); sal_Int32 nChars = 2; if( nCharWidth ) nChars = rTextPortion.GetSize().Width() / nCharWidth; @@ -3717,10 +4091,10 @@ void ImpEditEngine::Paint( OutputDevice& rOutDev, tools::Rectangle aClipRect, Po else if ( nChars == 2 ) nChars = 3; // looks better - OUStringBuffer aBuf; + OUStringBuffer aBuf(nChars); comphelper::string::padToLength(aBuf, nChars, rTextPortion.GetExtraValue()); OUString aText(aBuf.makeStringAndClear()); - aTmpFont.QuickDrawText( &rOutDev, aTmpPos, aText, 0, aText.getLength() ); + aTmpFont.QuickDrawText( &rOutDev, aTmpPos, aText, 0, aText.getLength(), {} ); rOutDev.DrawStretchText( aTmpPos, rTextPortion.GetSize().Width(), aText ); if ( bStripOnly ) @@ -3736,7 +4110,7 @@ void ImpEditEngine::Paint( OutputDevice& rOutDev, tools::Rectangle aClipRect, Po GetEditEnginePtr()->DrawingTab( aTmpPos, rTextPortion.GetSize().Width(), OUString(rTextPortion.GetExtraValue()), - aTmpFont, n, rTextPortion.GetRightToLeftLevel(), + aTmpFont, nParaPortion, rTextPortion.GetRightToLeftLevel(), bEndOfLine, bEndOfParagraph, aOverlineColor, aTextLineColor); } @@ -3753,8 +4127,8 @@ void ImpEditEngine::Paint( OutputDevice& rOutDev, tools::Rectangle aClipRect, Po const Color aTextLineColor(rOutDev.GetTextLineColor()); GetEditEnginePtr()->DrawingText( - aTmpPos, OUString(), 0, 0, nullptr, - aTmpFont, n, 0, + aTmpPos, OUString(), 0, 0, {}, {}, + aTmpFont, nParaPortion, 0, nullptr, nullptr, bEndOfLine, bEndOfParagraph, @@ -3774,7 +4148,7 @@ void ImpEditEngine::Paint( OutputDevice& rOutDev, tools::Rectangle aClipRect, Po } } - if ( ( nLine != nLastLine ) && !aStatus.IsOutliner() ) + if ((nLine != nLastLine ) && !maStatus.IsOutliner()) { adjustYDirectionAware(aStartPos, nSBL); } @@ -3784,10 +4158,10 @@ void ImpEditEngine::Paint( OutputDevice& rOutDev, tools::Rectangle aClipRect, Po break; } - if ( !aStatus.IsOutliner() ) + if (!maStatus.IsOutliner()) { - const SvxULSpaceItem& rULItem = rPortion.GetNode()->GetContentAttribs().GetItem( EE_PARA_ULSPACE ); - tools::Long nUL = GetYValue( rULItem.GetLower() ); + const SvxULSpaceItem& rULItem = rParaPortion.GetNode()->GetContentAttribs().GetItem( EE_PARA_ULSPACE ); + tools::Long nUL = scaleYSpacingValue(rULItem.GetLower()); adjustYDirectionAware(aStartPos, nUL); } @@ -3802,8 +4176,8 @@ void ImpEditEngine::Paint( OutputDevice& rOutDev, tools::Rectangle aClipRect, Po const Color aTextLineColor(rOutDev.GetTextLineColor()); GetEditEnginePtr()->DrawingText( - aTmpPos, OUString(), 0, 0, nullptr, - aTmpFont, n, 0, + aTmpPos, OUString(), 0, 0, {}, {}, + aTmpFont, nParaPortion, 0, nullptr, nullptr, false, true, // support for EOL/EOP TEXT comments @@ -3879,19 +4253,21 @@ void ImpEditEngine::Paint( ImpEditView* pView, const tools::Rectangle& rRect, Ou pView->DrawSelectionXOR(pView->GetEditSelection(), nullptr, &rTarget); } -void ImpEditEngine::InsertContent( ContentNode* pNode, sal_Int32 nPos ) +void ImpEditEngine::InsertContent(std::unique_ptr<ContentNode> pNode, sal_Int32 nPos ) { DBG_ASSERT( pNode, "NULL-Pointer in InsertContent! " ); DBG_ASSERT( IsInUndo(), "InsertContent only for Undo()!" ); - GetParaPortions().Insert(nPos, ParaPortion( pNode )); - aEditDoc.Insert(nPos, pNode); + + GetParaPortions().Insert(nPos, std::make_unique<ParaPortion>(pNode.get())); + maEditDoc.Insert(nPos, std::move(pNode)); + if ( IsCallParaInsertedOrDeleted() ) GetEditEnginePtr()->ParagraphInserted( nPos ); } EditPaM ImpEditEngine::SplitContent( sal_Int32 nNode, sal_Int32 nSepPos ) { - ContentNode* pNode = aEditDoc.GetObject( nNode ); + ContentNode* pNode = maEditDoc.GetObject( nNode ); DBG_ASSERT( pNode, "Invalid Node in SplitContent" ); DBG_ASSERT( IsInUndo(), "SplitContent only for Undo()!" ); DBG_ASSERT( nSepPos <= pNode->Len(), "Index out of range: SplitContent" ); @@ -3901,8 +4277,8 @@ EditPaM ImpEditEngine::SplitContent( sal_Int32 nNode, sal_Int32 nSepPos ) EditPaM ImpEditEngine::ConnectContents( sal_Int32 nLeftNode, bool bBackward ) { - ContentNode* pLeftNode = aEditDoc.GetObject( nLeftNode ); - ContentNode* pRightNode = aEditDoc.GetObject( nLeftNode+1 ); + ContentNode* pLeftNode = maEditDoc.GetObject( nLeftNode ); + ContentNode* pRightNode = maEditDoc.GetObject( nLeftNode+1 ); DBG_ASSERT( pLeftNode, "Invalid left node in ConnectContents "); DBG_ASSERT( pRightNode, "Invalid right node in ConnectContents "); return ImpConnectParagraphs( pLeftNode, pRightNode, bBackward ); @@ -3910,8 +4286,8 @@ EditPaM ImpEditEngine::ConnectContents( sal_Int32 nLeftNode, bool bBackward ) bool ImpEditEngine::SetUpdateLayout( bool bUp, EditView* pCurView, bool bForceUpdate ) { - const bool bPrevUpdateLayout = bUpdateLayout; - const bool bChanged = (bUpdateLayout != bUp); + const bool bPrevUpdateLayout = mbUpdateLayout; + const bool mbChanged = (mbUpdateLayout != bUp); // When switching from true to false, all selections were visible, // => paint over @@ -3919,8 +4295,8 @@ bool ImpEditEngine::SetUpdateLayout( bool bUp, EditView* pCurView, bool bForceUp // If !bFormatted, e.g. after SetText, then if UpdateMode=true // formatting is not needed immediately, probably because more text is coming. // At latest it is formatted at a Paint/CalcTextWidth. - bUpdateLayout = bUp; - if ( bUpdateLayout && ( bChanged || bForceUpdate ) ) + mbUpdateLayout = bUp; + if ( mbUpdateLayout && ( mbChanged || bForceUpdate ) ) FormatAndLayout( pCurView ); return bPrevUpdateLayout; } @@ -3938,13 +4314,13 @@ void ImpEditEngine::ShowParagraph( sal_Int32 nParagraph, bool bShow ) { // Mark as deleted, so that no selection will end or begin at // this paragraph... - aDeletedNodes.push_back(std::make_unique<DeletedNodeInfo>( pPPortion->GetNode(), nParagraph )); + maDeletedNodes.push_back(std::make_unique<DeletedNodeInfo>( pPPortion->GetNode(), nParagraph )); UpdateSelections(); // The region below will not be invalidated if UpdateMode = sal_False! // If anyway, then save as sal_False before SetVisible ! } - if ( bShow && ( pPPortion->IsInvalid() || !pPPortion->nHeight ) ) + if (bShow && (pPPortion->IsInvalid() || !pPPortion->GetHeight())) { if ( !GetTextRanger() ) { @@ -3954,21 +4330,21 @@ void ImpEditEngine::ShowParagraph( sal_Int32 nParagraph, bool bShow ) } else { - CalcHeight( pPPortion ); + CalcHeight(*pPPortion); } - nCurTextHeight += pPPortion->GetHeight(); + mnCurTextHeight += pPPortion->GetHeight(); } else { - nCurTextHeight = 0x7fffffff; + mnCurTextHeight = 0x7fffffff; } } pPPortion->SetMustRepaint( true ); if ( IsUpdateLayout() && !IsInUndo() && !GetTextRanger() ) { - aInvalidRect = tools::Rectangle( Point( 0, GetParaPortions().GetYOffset( pPPortion ) ), - Point( GetPaperSize().Width(), nCurTextHeight ) ); + maInvalidRect = tools::Rectangle( Point( 0, GetParaPortions().GetYOffset( pPPortion ) ), + Point( GetPaperSize().Width(), mnCurTextHeight ) ); UpdateViews( GetActiveView() ); } } @@ -3978,12 +4354,12 @@ EditSelection ImpEditEngine::MoveParagraphs( Range aOldPositions, sal_Int32 nNew DBG_ASSERT( GetParaPortions().Count() != 0, "No paragraphs found: MoveParagraphs" ); if ( GetParaPortions().Count() == 0 ) return EditSelection(); - aOldPositions.Justify(); + aOldPositions.Normalize(); EditSelection aSel( ImpMoveParagraphs( aOldPositions, nNewPos ) ); - if ( nNewPos >= GetParaPortions().Count() ) - nNewPos = GetParaPortions().Count() - 1; + if (nNewPos >= GetParaPortions().Count()) + nNewPos = GetParaPortions().lastIndex(); // Where the paragraph was inserted it has to be properly redrawn: // Where the paragraph was removed it has to be properly redrawn: @@ -3999,11 +4375,11 @@ EditSelection ImpEditEngine::MoveParagraphs( Range aOldPositions, sal_Int32 nNew ParaPortion* pLowerPortion = GetParaPortions().SafeGetObject( nLastPortion ); if (pUpperPortion && pLowerPortion) { - aInvalidRect = tools::Rectangle(); // make empty - aInvalidRect.SetLeft( 0 ); - aInvalidRect.SetRight(GetColumnWidth(aPaperSize)); - aInvalidRect.SetTop( GetParaPortions().GetYOffset( pUpperPortion ) ); - aInvalidRect.SetBottom( GetParaPortions().GetYOffset( pLowerPortion ) + pLowerPortion->GetHeight() ); + maInvalidRect = tools::Rectangle(); // make empty + maInvalidRect.SetLeft( 0 ); + maInvalidRect.SetRight(GetColumnWidth(maPaperSize)); + maInvalidRect.SetTop( GetParaPortions().GetYOffset( pUpperPortion ) ); + maInvalidRect.SetBottom( GetParaPortions().GetYOffset( pLowerPortion ) + pLowerPortion->GetHeight() ); UpdateViews( pCurView ); } @@ -4021,17 +4397,18 @@ void ImpEditEngine::InvalidateFromParagraph( sal_Int32 nFirstInvPara ) { // The following paragraphs are not invalidated, since ResetHeight() // => size change => all the following are re-issued anyway. - if ( nFirstInvPara != 0 ) + + if (nFirstInvPara != 0) { - ParaPortion& rTmpPortion = GetParaPortions()[nFirstInvPara-1]; - rTmpPortion.MarkInvalid( rTmpPortion.GetNode()->Len(), 0 ); - rTmpPortion.ResetHeight(); + ParaPortion& rPortion = GetParaPortions().getRef(nFirstInvPara - 1); + rPortion.MarkInvalid(rPortion.GetNode()->Len(), 0); + rPortion.ResetHeight(); } else { - ParaPortion& rTmpPortion = GetParaPortions()[0]; - rTmpPortion.MarkSelectionInvalid( 0 ); - rTmpPortion.ResetHeight(); + ParaPortion& rPortion = GetParaPortions().getRef(0); + rPortion.MarkSelectionInvalid(0); + rPortion.ResetHeight(); } } @@ -4042,41 +4419,45 @@ IMPL_LINK_NOARG(ImpEditEngine, StatusTimerHdl, Timer *, void) void ImpEditEngine::CallStatusHdl() { - if ( aStatusHdlLink.IsSet() && bool(aStatus.GetStatusWord()) ) + if (maStatusHdlLink.IsSet() && bool(maStatus.GetStatusWord())) { // The Status has to be reset before the Call, // since other Flags might be set in the handler... - EditStatus aTmpStatus( aStatus ); - aStatus.Clear(); - aStatusHdlLink.Call( aTmpStatus ); - aStatusTimer.Stop(); // If called by hand... + EditStatus aTmpStatus( maStatus ); + maStatus.Clear(); + maStatusHdlLink.Call( aTmpStatus ); + maStatusTimer.Stop(); // If called by hand... } } ContentNode* ImpEditEngine::GetPrevVisNode( ContentNode const * pCurNode ) { - const ParaPortion& rPortion1 = FindParaPortion( pCurNode ); - const ParaPortion* pPortion2 = GetPrevVisPortion( &rPortion1 ); - if ( pPortion2 ) - return pPortion2->GetNode(); + const ParaPortion* pPortion = FindParaPortion( pCurNode ); + DBG_ASSERT( pPortion, "GetPrevVisibleNode: No matching portion!" ); + pPortion = GetPrevVisPortion( pPortion ); + if ( pPortion ) + return pPortion->GetNode(); return nullptr; } ContentNode* ImpEditEngine::GetNextVisNode( ContentNode const * pCurNode ) { - const ParaPortion& rPortion = FindParaPortion( pCurNode ); - const ParaPortion* pPortion = GetNextVisPortion( &rPortion ); + const ParaPortion* pPortion = FindParaPortion( pCurNode ); + DBG_ASSERT( pPortion, "GetNextVisibleNode: No matching portion!" ); + pPortion = GetNextVisPortion( pPortion ); if ( pPortion ) return pPortion->GetNode(); return nullptr; } -const ParaPortion* ImpEditEngine::GetPrevVisPortion( const ParaPortion* pCurPortion ) const +const ParaPortion* ImpEditEngine::GetPrevVisPortion( const ParaPortion* pCurPortion) const { - sal_Int32 nPara = GetParaPortions().GetPos( pCurPortion ); - const ParaPortion* pPortion = nPara ? &GetParaPortions()[--nPara] : nullptr; - while ( pPortion && !pPortion->IsVisible() ) - pPortion = nPara ? &GetParaPortions()[--nPara] : nullptr; + sal_Int32 nPara = GetParaPortions().GetPos(pCurPortion); + DBG_ASSERT(GetParaPortions().exists(nPara) , "Portion not found: GetPrevVisPortion"); + + const ParaPortion* pPortion = nPara ? GetParaPortions().SafeGetObject(--nPara) : nullptr; + while (pPortion && !pPortion->IsVisible()) + pPortion = nPara ? GetParaPortions().SafeGetObject(--nPara) : nullptr; return pPortion; } @@ -4105,17 +4486,17 @@ tools::Long ImpEditEngine::CalcVertLineSpacing(Point& rStartPos) const // All paragraphs must have the block justification set. return 0; - const ParaPortion* pPortion = &rParaPortions[i]; - nTotalOccupiedHeight += pPortion->GetFirstLineOffset(); + ParaPortion const& rPortion = rParaPortions.getRef(i); + nTotalOccupiedHeight += rPortion.GetFirstLineOffset(); - const SvxLineSpacingItem& rLSItem = pPortion->GetNode()->GetContentAttribs().GetItem(EE_PARA_SBL); + const SvxLineSpacingItem& rLSItem = rPortion.GetNode()->GetContentAttribs().GetItem(EE_PARA_SBL); sal_uInt16 nSBL = ( rLSItem.GetInterLineSpaceRule() == SvxInterLineSpaceRule::Fix ) - ? GetYValue( rLSItem.GetInterLineSpace() ) : 0; + ? scaleYSpacingValue(rLSItem.GetInterLineSpace()) : 0; - const SvxULSpaceItem& rULItem = pPortion->GetNode()->GetContentAttribs().GetItem(EE_PARA_ULSPACE); - tools::Long nUL = GetYValue( rULItem.GetLower() ); + const SvxULSpaceItem& rULItem = rPortion.GetNode()->GetContentAttribs().GetItem(EE_PARA_ULSPACE); + tools::Long nUL = scaleYSpacingValue(rULItem.GetLower()); - const EditLineList& rLines = pPortion->GetLines(); + const EditLineList& rLines = rPortion.GetLines(); sal_Int32 nLineCount = rLines.Count(); nTotalLineCount += nLineCount; for (sal_Int32 j = 0; j < nLineCount; ++j) @@ -4128,7 +4509,7 @@ tools::Long ImpEditEngine::CalcVertLineSpacing(Point& rStartPos) const } } - tools::Long nTotalSpace = getHeightDirectionAware(aPaperSize); + tools::Long nTotalSpace = getHeightDirectionAware(maPaperSize); nTotalSpace -= nTotalOccupiedHeight; if (nTotalSpace <= 0 || nTotalLineCount <= 1) return 0; @@ -4173,17 +4554,19 @@ std::optional<EditSelection> ImpEditEngine::SelectParagraph( sal_Int32 nPara ) void ImpEditEngine::FormatAndLayout( EditView* pCurView, bool bCalledFromUndo ) { - if ( bDowning ) - return ; + if (mbDowning) + return; if ( IsInUndo() ) IdleFormatAndLayout( pCurView ); else { if (bCalledFromUndo) + { // in order to make bullet points that have had their styles changed, redraw themselves - for ( sal_Int32 nPortion = 0; nPortion < GetParaPortions().Count(); nPortion++ ) - GetParaPortions()[nPortion].MarkInvalid( 0, 0 ); + for (auto& pParaPortion : GetParaPortions()) + pParaPortion->MarkInvalid(0, 0); + } FormatDoc(); UpdateViews( pCurView ); } @@ -4194,44 +4577,41 @@ void ImpEditEngine::FormatAndLayout( EditView* pCurView, bool bCalledFromUndo ) void ImpEditEngine::SetFlatMode( bool bFlat ) { - if ( bFlat != aStatus.UseCharAttribs() ) + if ( bFlat != maStatus.UseCharAttribs() ) return; if ( !bFlat ) - aStatus.TurnOnFlags( EEControlBits::USECHARATTRIBS ); + maStatus.TurnOnFlags( EEControlBits::USECHARATTRIBS ); else - aStatus.TurnOffFlags( EEControlBits::USECHARATTRIBS ); + maStatus.TurnOffFlags( EEControlBits::USECHARATTRIBS ); - aEditDoc.CreateDefFont( !bFlat ); + maEditDoc.CreateDefFont( !bFlat ); FormatFullDoc(); UpdateViews(); - if ( pActiveView ) - pActiveView->ShowCursor(); + if (mpActiveView) + mpActiveView->ShowCursor(); } -void ImpEditEngine::SetCharStretching( sal_uInt16 nX, sal_uInt16 nY ) +void ImpEditEngine::setScalingParameters(ScalingParameters const& rScalingParameters) { - bool bChanged; - if ( !IsEffectivelyVertical() ) - { - bChanged = nStretchX!=nX || nStretchY!=nY; - nStretchX = nX; - nStretchY = nY; - } - else + ScalingParameters aNewScalingParameters(rScalingParameters); + + if (IsEffectivelyVertical()) { - bChanged = nStretchX!=nY || nStretchY!=nX; - nStretchX = nY; - nStretchY = nX; + std::swap(aNewScalingParameters.fFontX, aNewScalingParameters.fFontY); + std::swap(aNewScalingParameters.fSpacingX, aNewScalingParameters.fSpacingY); } - if (bChanged && aStatus.DoStretch()) + bool bScalingChanged = maScalingParameters != aNewScalingParameters; + maCustomScalingParameters = maScalingParameters = aNewScalingParameters; + + if (bScalingChanged && maStatus.DoStretch()) { FormatFullDoc(); // (potentially) need everything redrawn - aInvalidRect=tools::Rectangle(0,0,1000000,1000000); - UpdateViews( GetActiveView() ); + maInvalidRect = tools::Rectangle(0, 0, 1000000, 1000000); + UpdateViews(GetActiveView()); } } @@ -4250,7 +4630,7 @@ const SvxNumberFormat* ImpEditEngine::GetNumberFormat( const ContentNode *pNode // object to provide // access to the SvxNumberFormat of the Outliner. // The EditEngine implementation will just return 0. - pRes = pEditEngine->GetNumberFormat( nPara ); + pRes = mpEditEngine->GetNumberFormat( nPara ); } } @@ -4288,23 +4668,21 @@ sal_Int32 ImpEditEngine::GetSpaceBeforeAndMinLabelWidth( const SvxLRSpaceItem& ImpEditEngine::GetLRSpaceItem( ContentNode* pNode ) { - return pNode->GetContentAttribs().GetItem( aStatus.IsOutliner() ? EE_PARA_OUTLLRSPACE : EE_PARA_LRSPACE ); + return pNode->GetContentAttribs().GetItem( maStatus.IsOutliner() ? EE_PARA_OUTLLRSPACE : EE_PARA_LRSPACE ); } // select a representative text language for the digit type according to the // text numeral setting: -LanguageType ImpEditEngine::ImplCalcDigitLang(LanguageType eCurLang) const +LanguageType ImpEditEngine::ImplCalcDigitLang(LanguageType eCurLang) { - if (utl::ConfigManager::IsFuzzing()) + if (comphelper::IsFuzzing()) return LANGUAGE_ENGLISH_US; // #114278# Also setting up digit language from Svt options // (cannot reliably inherit the outdev's setting) - if( !pCTLOptions ) - pCTLOptions.reset( new SvtCTLOptions ); LanguageType eLang = eCurLang; - const SvtCTLOptions::TextNumerals nCTLTextNumerals = pCTLOptions->GetCTLTextNumerals(); + const SvtCTLOptions::TextNumerals nCTLTextNumerals = SvtCTLOptions::GetCTLTextNumerals(); if ( SvtCTLOptions::NUMERALS_HINDI == nCTLTextNumerals ) eLang = LANGUAGE_ARABIC_SAUDI_ARABIA; @@ -4383,42 +4761,56 @@ void ImpEditEngine::ImplInitLayoutMode(OutputDevice& rOutDev, sal_Int32 nPara, s Reference < i18n::XBreakIterator > const & ImpEditEngine::ImplGetBreakIterator() const { - if ( !xBI.is() ) + if (!mxBI.is()) { - Reference< uno::XComponentContext > xContext( ::comphelper::getProcessComponentContext() ); - xBI = i18n::BreakIterator::create( xContext ); + uno::Reference<uno::XComponentContext> xContext(::comphelper::getProcessComponentContext()); + mxBI = i18n::BreakIterator::create(xContext); } - return xBI; + return mxBI; } Reference < i18n::XExtendedInputSequenceChecker > const & ImpEditEngine::ImplGetInputSequenceChecker() const { - if ( !xISC.is() ) + if (!mxISC.is()) { - Reference< uno::XComponentContext > xContext( ::comphelper::getProcessComponentContext() ); - xISC = i18n::InputSequenceChecker::create( xContext ); + uno::Reference<uno::XComponentContext> xContext(::comphelper::getProcessComponentContext()); + mxISC = i18n::InputSequenceChecker::create(xContext); } - return xISC; + return mxISC; } Color ImpEditEngine::GetAutoColor() const { - Color aColor = GetColorConfig().GetColorValue(svtools::FONTCOLOR).nColor; + Color aColor; - if ( GetBackgroundColor() != COL_AUTO ) + if (comphelper::LibreOfficeKit::isActive() && SfxViewShell::Current()) { - if ( GetBackgroundColor().IsDark() && aColor.IsDark() ) + // Get document background color from current view instead + aColor = SfxViewShell::Current()->GetColorConfigColor(svtools::DOCCOLOR); + if (aColor.IsDark()) aColor = COL_WHITE; - else if ( GetBackgroundColor().IsBright() && aColor.IsBright() ) + else aColor = COL_BLACK; } + else + { + aColor = GetColorConfig().GetColorValue(svtools::FONTCOLOR).nColor; + + if ( GetBackgroundColor() != COL_AUTO ) + { + if ( GetBackgroundColor().IsDark() && aColor.IsDark() ) + aColor = COL_WHITE; + else if ( GetBackgroundColor().IsBright() && aColor.IsBright() ) + aColor = COL_BLACK; + } + } return aColor; } bool ImpEditEngine::ImplCalcAsianCompression(ContentNode* pNode, TextPortion* pTextPortion, sal_Int32 nStartPos, - tools::Long* pDXArray, sal_uInt16 n100thPercentFromMax, + sal_Int32* pDXArray, sal_uInt16 n100thPercentFromMax, bool bManipulateDXArray) { DBG_ASSERT( GetAsianCompressionMode() != CharCompressType::NONE, "ImplCalcAsianCompression - Why?" ); @@ -4528,7 +4920,7 @@ bool ImpEditEngine::ImplCalcAsianCompression(ContentNode* pNode, if ( bCompressed && ( n100thPercentFromMax == 10000 ) ) pTextPortion->GetExtraInfos()->nWidthFullCompression = nNewPortionWidth; - pTextPortion->GetSize().setWidth( nNewPortionWidth ); + pTextPortion->setWidth(nNewPortionWidth); if ( pTextPortion->GetExtraInfos() && ( n100thPercentFromMax != 10000 ) ) { @@ -4538,21 +4930,21 @@ bool ImpEditEngine::ImplCalcAsianCompression(ContentNode* pNode, nShrink /= 10000; tools::Long nNewWidth = pTextPortion->GetExtraInfos()->nOrgWidth - nShrink; if ( nNewWidth < pTextPortion->GetSize().Width() ) - pTextPortion->GetSize().setWidth( nNewWidth ); + pTextPortion->setWidth(nNewWidth); } } return bCompressed; } -void ImpEditEngine::ImplExpandCompressedPortions( EditLine* pLine, ParaPortion* pParaPortion, tools::Long nRemainingWidth ) +void ImpEditEngine::ImplExpandCompressedPortions(EditLine& rLine, ParaPortion& rParaPortion, tools::Long nRemainingWidth) { bool bFoundCompressedPortion = false; tools::Long nCompressed = 0; std::vector<TextPortion*> aCompressedPortions; - sal_Int32 nPortion = pLine->GetEndPortion(); - TextPortion* pTP = &pParaPortion->GetTextPortions()[ nPortion ]; + sal_Int32 nPortion = rLine.GetEndPortion(); + TextPortion* pTP = &rParaPortion.GetTextPortions()[ nPortion ]; while ( pTP && ( pTP->GetKind() == PortionKind::TEXT ) ) { if ( pTP->GetExtraInfos() && pTP->GetExtraInfos()->bCompressed ) @@ -4561,7 +4953,7 @@ void ImpEditEngine::ImplExpandCompressedPortions( EditLine* pLine, ParaPortion* nCompressed += pTP->GetExtraInfos()->nOrgWidth - pTP->GetSize().Width(); aCompressedPortions.push_back(pTP); } - pTP = ( nPortion > pLine->GetStartPortion() ) ? &pParaPortion->GetTextPortions()[ --nPortion ] : nullptr; + pTP = ( nPortion > rLine.GetStartPortion() ) ? &rParaPortion.GetTextPortions()[ --nPortion ] : nullptr; } if ( !bFoundCompressedPortion ) @@ -4580,16 +4972,16 @@ void ImpEditEngine::ImplExpandCompressedPortions( EditLine* pLine, ParaPortion* { pTP = pTP2; pTP->GetExtraInfos()->bCompressed = false; - pTP->GetSize().setWidth( pTP->GetExtraInfos()->nOrgWidth ); + pTP->setWidth(pTP->GetExtraInfos()->nOrgWidth); if ( nCompressPercent ) { - sal_Int32 nTxtPortion = pParaPortion->GetTextPortions().GetPos( pTP ); - sal_Int32 nTxtPortionStart = pParaPortion->GetTextPortions().GetStartPos( nTxtPortion ); - DBG_ASSERT( nTxtPortionStart >= pLine->GetStart(), "Portion doesn't belong to the line!!!" ); - tools::Long* pDXArray = pLine->GetCharPosArray().data() + (nTxtPortionStart - pLine->GetStart()); + sal_Int32 nTxtPortion = rParaPortion.GetTextPortions().GetPos( pTP ); + sal_Int32 nTxtPortionStart = rParaPortion.GetTextPortions().GetStartPos( nTxtPortion ); + DBG_ASSERT( nTxtPortionStart >= rLine.GetStart(), "Portion doesn't belong to the line!!!" ); + sal_Int32* pDXArray = rLine.GetCharPosArray().data() + (nTxtPortionStart - rLine.GetStart()); if ( pTP->GetExtraInfos()->pOrgDXArray ) memcpy( pDXArray, pTP->GetExtraInfos()->pOrgDXArray.get(), (pTP->GetLen()-1)*sizeof(sal_Int32) ); - ImplCalcAsianCompression( pParaPortion->GetNode(), pTP, nTxtPortionStart, pDXArray, static_cast<sal_uInt16>(nCompressPercent), true ); + ImplCalcAsianCompression( rParaPortion.GetNode(), pTP, nTxtPortionStart, pDXArray, static_cast<sal_uInt16>(nCompressPercent), true ); } } } @@ -4599,15 +4991,16 @@ void ImpEditEngine::ImplUpdateOverflowingParaNum(tools::Long nPaperHeight) tools::Long nY = 0; tools::Long nPH; - for ( sal_Int32 nPara = 0; nPara < GetParaPortions().Count(); nPara++ ) { - ParaPortion& rPara = GetParaPortions()[nPara]; - nPH = rPara.GetHeight(); + for (sal_Int32 nPara = 0; nPara < GetParaPortions().Count(); nPara++) + { + ParaPortion& rParaPortion = GetParaPortions().getRef(nPara); + nPH = rParaPortion.GetHeight(); nY += nPH; - if ( nY > nPaperHeight /*nCurTextHeight*/ ) // found first paragraph overflowing + if ( nY > nPaperHeight /*mnCurTextHeight*/ ) // found first paragraph overflowing { mnOverflowingPara = nPara; SAL_INFO("editeng.chaining", "[CHAINING] Setting first overflowing #Para#: " << nPara); - ImplUpdateOverflowingLineNum( nPaperHeight, nPara, nY-nPH); + ImplUpdateOverflowingLineNum( nPaperHeight, nPara, nY - nPH); return; } } @@ -4617,21 +5010,26 @@ void ImpEditEngine::ImplUpdateOverflowingLineNum(tools::Long nPaperHeight, sal_uInt32 nOverflowingPara, tools::Long nHeightBeforeOverflowingPara) { + if (GetParaPortions().exists(nOverflowingPara)) + return; + tools::Long nY = nHeightBeforeOverflowingPara; tools::Long nLH; - ParaPortion& rPara = GetParaPortions()[nOverflowingPara]; + ParaPortion& rParaPortion = GetParaPortions().getRef(nOverflowingPara); // Like UpdateOverflowingParaNum but for each line in the first // overflowing paragraph. - for ( sal_Int32 nLine = 0; nLine < rPara.GetLines().Count(); nLine++ ) { + for (sal_Int32 nLine = 0; nLine < rParaPortion.GetLines().Count(); nLine++) + { // XXX: We must use a reference here because the copy constructor resets the height - EditLine &aLine = rPara.GetLines()[nLine]; + EditLine &aLine = rParaPortion.GetLines()[nLine]; nLH = aLine.GetHeight(); nY += nLH; // Debugging output - if (nLine == 0) { + if (nLine == 0) + { SAL_INFO("editeng.chaining", "[CHAINING] First line has height " << nLH); } |