/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ /* * This file is part of the LibreOffice project. * * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. * * This file incorporates work covered by the following license notice: * * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed * with this work for additional information regarding copyright * ownership. The ASF licenses this file to you under the Apache * License, Version 2.0 (the "License"); you may not use this file * except in compliance with the License. You may obtain a copy of * the License at http://www.apache.org/licenses/LICENSE-2.0 .
*/
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 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 0xFF08: case 0xFF0C: case 0xFF0E: case 0xFF1A: case 0xFF1B: case 0xFF3B: case 0xFF5B:
{ return AsianCompressionFlags::PunctuationLeft;
} default:
{ return ( ( 0x3040 <= cChar ) && ( 0x3100 > cChar ) ) ? AsianCompressionFlags::Kana : AsianCompressionFlags::Normal;
}
}
}
staticvoid lcl_DrawRedLines( OutputDevice& rOutDev,
tools::Long nFontHeight, const Point& rPoint,
size_t nIndex,
size_t nMaxEnd,
KernArraySpan pDXArray,
WrongList const * pWrongs,
Degree10 nOrientation, const Point& rOrigin, bool bVertical, bool bIsRightToLeft )
{ // But only if font is not too small...
tools::Long nHeight = rOutDev.LogicToPixel(Size(0, nFontHeight)).Height(); if (WRONG_SHOW_MIN >= nHeight) return;
if (nStart < nIndex) // Corrected
nStart = nIndex;
if (nEnd > nMaxEnd)
nEnd = nMaxEnd;
Point aPoint1(rPoint); if (bVertical)
{ // VCL doesn't know that the text is vertical, and is manipulating // the positions a little bit in y direction...
tools::Long nOnePixel = rOutDev.PixelToLogic(Size(0, 1)).Height();
tools::Long nCorrect = 2 * nOnePixel;
aPoint1.AdjustY(-nCorrect);
aPoint1.AdjustX(-nCorrect);
} if (nStart > nIndex)
{ if (!bVertical)
{ // since for RTL portions rPoint is on the visual right end of the portion // (i.e. at the start of the first RTL char) we need to subtract the offset // for RTL portions...
aPoint1.AdjustX((bIsRightToLeft ? -1 : 1) * pDXArray[nStart - nIndex - 1]);
} else
aPoint1.AdjustY(pDXArray[nStart - nIndex - 1]);
}
Point aPoint2(rPoint);
assert(nEnd > nIndex && "RedLine: aPnt2?"); if (!bVertical)
{ // since for RTL portions rPoint is on the visual right end of the portion // (i.e. at the start of the first RTL char) we need to subtract the offset // for RTL portions...
aPoint2.AdjustX((bIsRightToLeft ? -1 : 1) * pDXArray[nEnd - nIndex - 1]);
} else
{
aPoint2.AdjustY(pDXArray[nEnd - nIndex - 1]);
}
if (nOrientation)
{
rOrigin.RotateAround(aPoint1, nOrientation);
rOrigin.RotateAround(aPoint2, nOrientation);
}
// #i97146# check if that view is still available // else probably the idle format timer fired while we're already // downing
EditView* pView = maIdleFormatter.GetView(); for (EditView* aEditView : maEditViews)
{ if( aEditView == pView )
{
FormatAndLayout( pView ); break;
}
}
}
void ImpEditEngine::CheckIdleFormatter()
{
maIdleFormatter.ForceTimeout(); // If not idle, but still not formatted: if ( !IsFormatted() )
FormatDoc();
}
for (sal_Int32 nParagraph = 0; nParagraph < nParaCount; nParagraph++)
{
ParaPortion& rParaPortion = GetParaPortions().getRef(nParagraph); if (rParaPortion.MustRepaint() || (rParaPortion.IsInvalid() && rParaPortion.IsVisible()))
{ // No formatting should be necessary for MustRepaint()! if (CreateLines(nParagraph, nY))
{ if (!bGrow && GetTextRanger())
{ // For a change in height all below must be reformatted... for (sal_Int32 n = nParagraph + 1; n < nParaCount; n++)
{
ParaPortion& rParaPortionToInvalidate = GetParaPortions().getRef(n);
rParaPortionToInvalidate.MarkSelectionInvalid(0);
rParaPortionToInvalidate.GetLines().Reset();
}
}
bGrow = true; if (IsCallParaInsertedOrDeleted())
{
GetEditEnginePtr()->ParagraphHeightChanged(nParagraph);
for (EditView* pView : maEditViews)
{
pView->getImpl().ScrollStateChange();
}
}
rParaPortion.SetMustRepaint(false);
}
aRepaintParagraphList.insert(nParagraph);
}
nY += rParaPortion.GetHeight(); if (!isInEmptyClusterAtTheEnd(rParaPortion, bIsScaling))
nResult = nY; // The total height excluding trailing blank paragraphs
} return nResult;
}
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, true);
bOverflow = nHeight > (maMaxAutoPaperSize.Height() * mnColumns);
// Increase scale level
nCurrentScaleLevel++;
}
}
void ImpEditEngine::EnsureDocumentFormatted()
{ if (!IsFormatted())
FormatDoc();
}
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, false);
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 nNewHeight = CalcTextHeight();
tools::Long nDiff = nNewHeight - mnCurTextHeight; if ( nDiff )
{
maInvalidRect.Union(tools::Rectangle::Normalize(
{ 0, nNewHeight }, { getWidthDirectionAware(maPaperSize), mnCurTextHeight }));
maStatus.GetStatusWord() |= !IsEffectivelyVertical() ? EditStatusFlags::TextHeightChanged : EditStatusFlags::TEXTWIDTHCHANGED;
}
SetValidPaperSize( maPaperSize ); // consider Min, Max
if ( maPaperSize == aPrevPaperSize ) return;
if ( ( !IsEffectivelyVertical() && ( maPaperSize.Width() != aPrevPaperSize.Width() ) )
|| ( IsEffectivelyVertical() && ( maPaperSize.Height() != aPrevPaperSize.Height() ) ) )
{ // If ahead is centered / right or tabs...
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().getRef(nPara);
SvxAdjust eJustification = GetJustification( nPara ); if ( eJustification != SvxAdjust::Left )
{
rParaPortion.MarkSelectionInvalid(0);
CreateLines( nPara, 0 ); // 0: For AutoPageSize no TextRange!
}
}
}
if (nTxtHeight > nBoxHeight && !bOnlyOneEmptyPara)
{ // which paragraph is the first to cause higher size of the box?
ImplUpdateOverflowingParaNum( nBoxHeight); // XXX: currently only for horizontal text //maStatus.SetPageOverflow(true);
mbNeedsChainingHandling = true;
} else
{ // No overflow if within box boundaries //maStatus.SetPageOverflow(false);
mbNeedsChainingHandling = false;
}
bool ImpEditEngine::createLinesForEmptyParagraph(ParaPortion& rParaPortion)
{ // fast special treatment... if (rParaPortion.GetTextPortions().Count())
rParaPortion.GetTextPortions().Reset(); if (rParaPortion.GetLines().Count())
rParaPortion.GetLines().Reset();
// If PaperSize == long_max, one cannot take away any negative // first line indent. (Overflow) if (nMaxLineWidth < 0 && nStartX < 0)
nMaxLineWidth
= GetColumnWidth(maPaperSize) - scaleXSpacingValue(rLRItem.ResolveRight(rMetrics));
// If still less than 0, it may be just the right edge. if (nMaxLineWidth <= 0)
nMaxLineWidth = 1;
if (pTPRubyStart && nTextPos >= pNextRubyAttr->GetEnd())
{ auto pRubyInfo = std::make_unique<RubyPortionInfo>();
// Get ruby text width
// TODO: Style support is unimplemented. For now, use a hard-coded 50% scale
aRubyStartFont.SetFontSize(aRubyStartFont.GetFontSize() / 2);
aRubyStartFont.SetPhysFont(*GetRefDevice());
auto aRubyMetrics = GetRefDevice()->GetFontMetric(); auto nRubyAscent = static_cast<tools::Long>(aRubyMetrics.GetAscent());
bool ImpEditEngine::CreateLines( sal_Int32 nPara, sal_uInt32 nStartPosY )
{
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" );
DBG_ASSERT( rParaPortion.IsVisible(), "Invisible paragraphs not formatted!" );
DBG_ASSERT( rParaPortion.IsInvalid(), "CreateLines: Portion not invalid!" );
// Fast special treatment for empty paragraphs... 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 )
{ returnfalse;
}
//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...
if (rParaPortion.GetLines().Count() == 0)
{
rParaPortion.GetLines().Append(std::make_unique<EditLine>());
}
// Determine if quick format should be used if (!bEmptyNodeWithPolygon && !HasScriptType(nPara, i18n::ScriptType::COMPLEX))
{ if (rParaPortion.IsSimpleInvalid() &&
rParaPortion.GetInvalidDiff() > 0 &&
pNode->GetString().indexOf(CH_FEATURE, nInvalidStart) > nInvalidEnd)
{
bQuickFormat = true;
} elseif (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; for (autoconst& pTextPortion : rParaPortion.GetTextPortions())
{ // There must be no start / end in the deleted area.
nPos = nPos + pTextPortion->GetLen(); if (nPos > nStart && nPos < nEnd)
{
bQuickFormat = false; break;
}
}
}
}
// Saving both layout mode and language (since I'm potentially changing both)
GetRefDevice()->Push( vcl::PushFlags::TEXTLAYOUTMODE|vcl::PushFlags::TEXTLANGUAGE );
ImplInitLayoutMode(*GetRefDevice(), nPara, -1);
sal_Int32 nRealInvalidStart = nInvalidStart;
if (bEmptyNodeWithPolygon)
{
TextPortion* pDummyPortion = new TextPortion( 0 );
rParaPortion.GetTextPortions().Reset();
rParaPortion.GetTextPortions().Append(pDummyPortion);
} elseif ( bQuickFormat )
{ // faster Method:
RecalcTextPortion(rParaPortion, nInvalidStart, nInvalidDiff);
} else// nRealInvalidStart can be before InvalidStart, since Portions were deleted...
{
CreateTextPortions(rParaPortion, nRealInvalidStart);
}
// Search for line with InvalidPos, start one line before // Flag the line => do not remove it !
sal_Int32 nLine = rParaPortion.GetLines().Count()-1; for ( sal_Int32 nL = 0; nL <= nLine; nL++ )
{
EditLine& rLine = rParaPortion.GetLines()[nL]; if ( rLine.GetEnd() > nRealInvalidStart ) // not nInvalidStart!
{
nLine = nL; break;
}
rLine.SetValid();
} // 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)))
{
nLine--;
}
tools::Rectangle aBulletArea{Point(), Point()};
if (!nLine)
{
aBulletArea = GetEditEnginePtr()->GetBulletArea(GetParaPortions().GetPos(&rParaPortion)); if ( !aBulletArea.IsWidthEmpty() && aBulletArea.Right() > 0 )
rParaPortion.SetBulletX(sal_Int32(scaleXSpacingValue(aBulletArea.Right()))); else
rParaPortion.SetBulletX( 0 ); // if Bullet is set incorrectly
}
auto stMetrics = GetFontUnitMetrics(pNode);
tools::Long nStartX
= scaleXSpacingValue(rLRItem.ResolveTextLeft(stMetrics) + nSpaceBeforeAndMinLabelWidth); // Multiline hyperlink may need to know if the next line is bigger.
tools::Long nStartXNextLine = nStartX; if ( nIndex == 0 )
{
tools::Long nFI = scaleXSpacingValue(rLRItem.ResolveTextFirstLineOffset(stMetrics));
nStartX += nFI;
// Problem: // Since formatting starts a line _before_ the invalid position, // the positions unfortunately have to be redefined... // Solution: // The line before can only become longer, not smaller // =>...
pLine->GetCharPosArray().clear();
// tdf#162803: Stale kashida position data also needs to be cleared on each layout.
pLine->GetKashidaArray().clear();
nXWidth = 0; while ( !nXWidth )
{
tools::Long nYOff = nTextY + nTextExtraYOffset;
tools::Long nYDiff = nTextLineHeight; if ( IsEffectivelyVertical() )
{
tools::Long nMaxPolygonX = GetTextRanger()->GetBoundRect().Right();
nYOff = nMaxPolygonX-nYOff;
nYDiff = -nTextLineHeight;
}
pTextRanges = GetTextRanger()->GetTextRanges( Range( nYOff, nYOff + nYDiff ) );
assert( pTextRanges && "GetTextRanges?!" );
tools::Long nMaxRangeWidth = 0; // Use the widest range... // The widest range could be a bit confusing, so normally it // is the first one. Best with gaps.
assert(pTextRanges->size() % 2 == 0 && "textranges are always in pairs"); if (!pTextRanges->empty())
{
tools::Long nA = pTextRanges->at(0);
tools::Long nB = pTextRanges->at(1);
DBG_ASSERT( nA <= nB, "TextRange distorted?" );
tools::Long nW = nB - nA; if ( nW > nMaxRangeWidth )
{
nMaxRangeWidth = nW;
nTextXOffset = nA;
}
}
nXWidth = nMaxRangeWidth; if (nXWidth)
nMaxLineWidth
= nXWidth - nStartX - scaleXSpacingValue(rLRItem.ResolveRight(stMetrics)); else
{ // Try further down in the polygon. // Below the polygon use the Paper Width.
nTextExtraYOffset += std::max( static_cast<tools::Long>(nTextLineHeight / 10), tools::Long(1) ); if ( ( nTextY + nTextExtraYOffset ) > GetTextRanger()->GetBoundRect().Bottom() )
{
nXWidth = getWidthDirectionAware(GetPaperSize()); if ( !nXWidth ) // AutoPaperSize
nXWidth = 0x7FFFFFFF;
}
}
}
}
// search for Portion that no longer fits in line...
TextPortion* pPortion = nullptr;
sal_Int32 nPortionLen = 0; bool bContinueLastPortion = false; bool bBrokenLine = false;
bLineBreak = false; const EditCharAttrib* pNextFeature = pNode->GetCharAttribs().FindFeature( pLine->GetStart() ); while ( ( nTmpWidth < nXWidth ) && !bEOL )
{ const sal_Int32 nTextPortions = rParaPortion.GetTextPortions().Count();
assert(nTextPortions > 0);
bContinueLastPortion = (nTmpPortion >= nTextPortions); if (bContinueLastPortion)
{ if (nTmpPos >= pNode->Len()) break; // while
// Continue with remainder. This only to have *some* valid // X-values and not endlessly create new lines until DOOM... // Happened in the scenario of tdf#104152 where inserting a // paragraph lead to a11y attempting to format the doc to // obtain content when notified.
nTmpPortion = nTextPortions - 1;
SAL_WARN("editeng","ImpEditEngine::CreateLines - continuation of a broken portion");
}
nPortionStart = nTmpPos;
pPortion = &rParaPortion.GetTextPortions()[nTmpPortion]; if ( !bContinueLastPortion && pPortion->GetKind() == PortionKind::HYPHENATOR )
{ // Throw away a Portion, if necessary correct the one before, // if the Hyph portion has swallowed a character...
sal_Int32 nTmpLen = pPortion->GetLen();
rParaPortion.GetTextPortions().Remove( nTmpPortion ); if (nTmpPortion && nTmpLen)
{
nTmpPortion--;
TextPortion& rPrev = rParaPortion.GetTextPortions()[nTmpPortion];
DBG_ASSERT( rPrev.GetKind() == PortionKind::TEXT, "Portion?!" );
nTmpWidth -= rPrev.GetSize().Width();
nTmpPos = nTmpPos - rPrev.GetLen();
rPrev.SetLen(rPrev.GetLen() + nTmpLen);
rPrev.setWidth(-1);
}
assert(nTmpPortion < rParaPortion.GetTextPortions().Count() && "No more Portions left!");
pPortion = &rParaPortion.GetTextPortions()[nTmpPortion];
}
if (bContinueLastPortion)
{ // Note that this may point behind the portion and is only to // be used with the node's string offsets to generate X-values.
nPortionLen = pNode->Len() - nPortionStart;
} else
{
nPortionLen = pPortion->GetLen();
}
DBG_ASSERT( pPortion->GetKind() != PortionKind::HYPHENATOR, "CreateLines: Hyphenator-Portion!" );
DBG_ASSERT( nPortionLen || bProcessingEmptyLine, "Empty Portion in CreateLines ?!" ); if ( pNextFeature && ( pNextFeature->GetStart() == nTmpPos ) )
{
SAL_WARN_IF( bContinueLastPortion, "editeng","ImpEditEngine::CreateLines - feature in continued portion will be wrong");
sal_uInt16 nWhich = pNextFeature->GetItem()->Which(); switch ( nWhich )
{ case EE_FEATURE_TAB:
{
tools::Long nOldTmpWidth = nTmpWidth;
// If this is the first token on the line, // and nTmpWidth > maPaperSize.Width, => infinite loop! if ( ( nTmpWidth >= nXWidth ) && ( nTmpPortion == pLine->GetStartPortion() ) )
{ // What now? // make the tab fitting
pPortion->setWidth( nXWidth-nOldTmpWidth );
nTmpWidth = nXWidth-1;
bEOL = true;
bBrokenLine = true;
}
KernArray& rArray = pLine->GetCharPosArray();
size_t nPos = nTmpPos - pLine->GetStart();
rArray.insert(rArray.begin()+nPos, pPortion->GetSize().Width());
bCompressedChars = false;
} break; case EE_FEATURE_LINEBR:
{
assert( pPortion );
pPortion->setWidth(0);
bEOL = true;
bLineBreak = true;
pPortion->SetKind( PortionKind::LINEBREAK );
bCompressedChars = false;
KernArray& rArray = pLine->GetCharPosArray();
size_t nPos = nTmpPos - pLine->GetStart();
rArray.insert(rArray.begin()+nPos, pPortion->GetSize().Width());
} break; case EE_FEATURE_FIELD:
{
SeekCursor( pNode, nTmpPos+1, aTmpFont );
aTmpFont.SetPhysFont(*GetRefDevice());
ImplInitDigitMode(*GetRefDevice(), aTmpFont.GetLanguage());
OUString aFieldValue = static_cast<const EditCharAttribField*>(pNextFeature)->GetFieldValue(); // get size, but also DXArray to allow length information in line breaking below
KernArray aTmpDXArray;
pPortion->SetSize(aTmpFont.QuickGetTextSize(GetRefDevice(),
aFieldValue, 0, aFieldValue.getLength(), &aTmpDXArray));
// So no scrolling for oversized fields if (pPortion->GetSize().Width() > nXWidth - nTmpWidth)
{ // create ExtraPortionInfo on-demand, flush lineBreaksList
ExtraPortionInfo *pExtraInfo = pPortion->GetExtraInfos();
// iterate over CellBreaks using XBreakIterator to be on the // safe side with international texts/charSets
Reference < i18n::XBreakIterator > xBreakIterator(ImplGetBreakIterator()); const sal_Int32 nTextLength(aFieldValue.getLength()); const lang::Locale aLocale(GetLocale(EditPaM(pNode, nPortionStart)));
sal_Int32 nDone(0);
sal_Int32 nNextCellBreak(
xBreakIterator->nextCharacters(
aFieldValue,
0,
aLocale,
css::i18n::CharacterIteratorMode::SKIPCELL,
0,
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);
for(sal_Int32 a(0); a < nTextLength; a++)
{ if(a == nNextCellBreak)
{ // check width if(aTmpDXArray[a] - nLineStartX > nXWidth)
{ // new CellBreak does not fit in current line, need to // create a break at LastCellBreak - but do not add 1st // line break twice for very tall frames 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
nLineStartX = aTmpDXArray[nLastCellBreak];
}
// update CellBreak iteration values
nLastCellBreak = a;
nNextCellBreak = xBreakIterator->nextCharacters(
aFieldValue,
a,
aLocale,
css::i18n::CharacterIteratorMode::SKIPCELL,
1,
nDone);
}
} //next Line should start here... after this field end if (!bFieldStartNextLine)
nStartNextLineAfterMultiLineField
= aTmpDXArray[nTextLength - 1] - nLineStartX; elseif (pExtraInfo)
{ // if the 1. character does not fit, // but there is pExtraInfo, then delete it
pPortion->SetExtraInfos(nullptr);
pExtraInfo = nullptr;
}
}
nTmpWidth += pPortion->GetSize().Width();
KernArray& rArray = pLine->GetCharPosArray();
size_t nPos = nTmpPos - pLine->GetStart();
rArray.insert(rArray.begin()+nPos, pPortion->GetSize().Width());
pPortion->SetKind(PortionKind::FIELD); // If this is the first token on the line, // and nTmpWidth > maPaperSize.Width, => infinite loop! if ( ( nTmpWidth >= nXWidth ) && ( nTmpPortion == pLine->GetStartPortion() ) )
{
nTmpWidth = nXWidth-1;
bEOL = true;
bBrokenLine = true;
} // Compression in Fields???? // I think this could be a little bit difficult and is not very useful
bCompressedChars = false;
} break; default: OSL_FAIL( "What feature?" );
}
pNextFeature = pNode->GetCharAttribs().FindFeature( pNextFeature->GetStart() + 1 );
} else
{
DBG_ASSERT( nPortionLen || bProcessingEmptyLine, "Empty Portion - Extra Space?!" );
SeekCursor( pNode, nTmpPos+1, aTmpFont );
aTmpFont.SetPhysFont(*GetRefDevice());
ImplInitDigitMode(*GetRefDevice(), aTmpFont.GetLanguage());
if (!bContinueLastPortion)
pPortion->SetRightToLeftLevel( GetRightToLeft( nPara, nTmpPos+1 ) );
// this was possibly a portion too far: bool bFixedEnd = false; if (maStatus.OneCharPerLine())
{ // State before Portion (apart from nTmpWidth):
nTmpPos -= pPortion ? nPortionLen : 0;
nPortionStart = nTmpPos;
nTmpPortion--;
bEOL = true;
bEOC = false;
// And now just one character:
nTmpPos++;
nTmpPortion++;
nPortionEnd = nTmpPortion; // one Non-Feature-Portion has to be wrapped if ( pPortion && nPortionLen > 1 )
{
DBG_ASSERT( pPortion->GetKind() == PortionKind::TEXT, "Len>1, but no TextPortion?" );
nTmpWidth -= pPortion->GetSize().Width();
sal_Int32 nP = SplitTextPortion(rParaPortion, nTmpPos, pLine);
nTmpWidth += rParaPortion.GetTextPortions()[nP].GetSize().Width();
}
} elseif ( nTmpWidth >= nXWidth )
{
nPortionEnd = nTmpPos;
nTmpPos -= pPortion ? nPortionLen : 0;
nPortionStart = nTmpPos;
nTmpPortion--;
bEOL = false;
bEOC = false; if( pPortion ) switch ( pPortion->GetKind() )
{ case PortionKind::TEXT:
{
nTmpWidth -= pPortion->GetSize().Width();
} break; case PortionKind::FIELD: case PortionKind::TAB:
{
nTmpWidth -= pPortion->GetSize().Width();
bEOL = true;
bFixedEnd = true;
} break; default:
{ // A feature is not wrapped:
DBG_ASSERT( ( pPortion->GetKind() == PortionKind::LINEBREAK ), "What Feature ?" );
bEOL = true;
bFixedEnd = true;
}
}
} else
{
bEOL = true;
bEOC = true;
pLine->SetEnd( nPortionEnd );
assert(rParaPortion.GetTextPortions().Count() && "No TextPortions?");
pLine->SetEndPortion(rParaPortion.GetTextPortions().Count() - 1);
}
// The font metrics can not be calculated continuously, if the font is // set anyway, because a large font only after wrapping suddenly ends // up in the next line => Font metrics too big.
FormatterFontMetric aFormatterMetrics;
sal_Int32 nTPos = pLine->GetStart(); for ( sal_Int32 nP = pLine->GetStartPortion(); nP <= pLine->GetEndPortion(); nP++ )
{ const TextPortion& rTP = rParaPortion.GetTextPortions()[nP]; // problem with hard font height attribute, when everything but the line break has this attribute if ( rTP.GetKind() != PortionKind::LINEBREAK )
{
SeekCursor( pNode, nTPos+1, aTmpFont );
aTmpFont.SetPhysFont(*GetRefDevice());
ImplInitDigitMode(*GetRefDevice(), aTmpFont.GetLanguage());
RecalcFormatterFontMetrics( aFormatterMetrics, aTmpFont );
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(maPaperSize)
- scaleXSpacingValue(rLRItem.ResolveRight(stMetrics))
- nStartX; if ( aTextSize.Width() < nMaxLineWidthFix )
nMaxLineWidth = nMaxLineWidthFix;
}
if ( pLine->IsHangingPunctuation() )
{ // Width from HangingPunctuation was set to 0 in ImpBreakLine, // check for rel width now, maybe create compression...
tools::Long n = nMaxLineWidth - aTextSize.Width();
TextPortion& rTP = rParaPortion.GetTextPortions()[pLine->GetEndPortion()];
sal_Int32 nPosInArray = pLine->GetEnd()-1-pLine->GetStart();
tools::Long nNewValue = ( nPosInArray ? pLine->GetCharPosArray()[ nPosInArray-1 ] : 0 ) + n; if (o3tl::make_unsigned(nPosInArray) < pLine->GetCharPosArray().size())
{
pLine->GetCharPosArray()[ nPosInArray ] = nNewValue;
}
rTP.adjustSize(n, 0);
}
pLine->SetTextWidth( aTextSize.Width() ); switch ( eJustification )
{ case SvxAdjust::Center:
{
tools::Long n = ( nMaxLineWidth - aTextSize.Width() ) / 2;
n += nStartX; // Indentation is kept.
pLine->SetStartPosX( n );
} break; case SvxAdjust::Right:
{ // For automatically wrapped lines, which has a blank at the end // the blank must not be displayed!
tools::Long n = nMaxLineWidth - aTextSize.Width();
n += nStartX; // Indentation is kept.
pLine->SetStartPosX( n );
} break; case SvxAdjust::Block:
{ bool bDistLastLine = (GetJustifyMethod(nPara) == SvxCellJustifyMethod::Distribute);
tools::Long nRemainingSpace = nMaxLineWidth - aTextSize.Width();
pLine->SetStartPosX( nStartX ); if ( nRemainingSpace > 0 && (!bEOC || bDistLastLine) )
ImpAdjustBlocks(rParaPortion, *pLine, nRemainingSpace);
} break; default:
{
pLine->SetStartPosX( nStartX ); // FI, LI
} break;
}
// Check whether the line must be re-issued...
pLine->SetInvalid();
// If a portion was wrapped there may be far too many positions in // CharPosArray:
KernArray& rArray = pLine->GetCharPosArray();
size_t nLen = pLine->GetLen(); if (rArray.size() > nLen)
rArray.erase(rArray.begin()+nLen, rArray.end());
// for <0 think over ! if (rParaPortion.IsSimpleInvalid())
{ // Change through simple Text changes... // Do not cancel formatting since Portions possibly have to be split // again! If at some point cancelable, then validate the following // line! But if applicable, mark as valid, so there is less output... if ( pLine->GetEnd() < nInvalidStart )
{ if ( *pLine == aSaveLine )
{
pLine->SetValid();
}
} else
{
sal_Int32 nStart = pLine->GetStart();
sal_Int32 nEnd = pLine->GetEnd();
if ( nStart > nInvalidEnd )
{ if ( ( ( nStart-nInvalidDiff ) == aSaveLine.GetStart() ) &&
( ( nEnd-nInvalidDiff ) == aSaveLine.GetEnd() ) )
{
pLine->SetValid(); if (bQuickFormat)
{
bLineBreak = false;
rParaPortion.CorrectValuesBehindLastFormattedLine( nLine ); break;
}
}
} elseif (bQuickFormat && (nEnd > nInvalidEnd))
{ // If the invalid line ends so that the next begins on the // 'same' passage as before, i.e. not wrapped differently, // then the text width does not have to be determined anew: if ( nEnd == ( aSaveLine.GetEnd() + nInvalidDiff ) )
{
bLineBreak = false;
rParaPortion.CorrectValuesBehindLastFormattedLine( nLine ); break;
}
}
}
}
if ( !bSameLineAgain )
{
nIndex = pLine->GetEnd(); // next line start = last line end // as nEnd points to the last character!
// Next line or maybe a new line...
pLine = nullptr; if ( nLine < rParaPortion.GetLines().Count()-1 )
pLine = &rParaPortion.GetLines()[++nLine]; if ( pLine && ( nIndex >= pNode->Len() ) )
{
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, std::unique_ptr<EditLine>(pLine));
} elseif ( nIndex && bLineBreak && GetTextRanger() )
{ // normally CreateAndInsertEmptyLine would be called, but I want to use // CreateLines, so I need Polygon code only here...
TextPortion* pDummyPortion = new TextPortion( 0 );
rParaPortion.GetTextPortions().Append(pDummyPortion);
pLine = new EditLine;
rParaPortion.GetLines().Insert(++nLine, std::unique_ptr<EditLine>(pLine));
bForceOneRun = true;
bProcessingEmptyLine = true;
}
} if ( pLine )
{
aSaveLine = *pLine;
pLine->SetStart( nIndex );
pLine->SetEnd( nIndex );
pLine->SetStartPortion( nEndPortion+1 );
pLine->SetEndPortion( nEndPortion+1 );
}
}
} // while ( Index < Len )
if ( nDelFromLine >= 0 )
rParaPortion.GetLines().DeleteFromLine( nDelFromLine );
DBG_ASSERT(rParaPortion.GetLines().Count(), "No line after CreateLines!");
if ( bLineBreak )
CreateAndInsertEmptyLine(rParaPortion);
void ImpEditEngine::CreateAndInsertEmptyLine(ParaPortion& rParaPortion)
{
DBG_ASSERT( !GetTextRanger(), "Don't use CreateAndInsertEmptyLine with a polygon!" );
EditLine* pTmpLine = new EditLine;
pTmpLine->SetStart(rParaPortion.GetNode()->Len());
pTmpLine->SetEnd(rParaPortion.GetNode()->Len());
rParaPortion.GetLines().Append(std::unique_ptr<EditLine>(pTmpLine));
auto stMetrics = GetFontUnitMetrics(rParaPortion.GetNode());
// BUG in I18N - under special condition (break behind field, #87327#) breakIndex is < nMinBreakPos if ( nBreakPos < nMinBreakPos )
{
nBreakPos = nMinBreakPos;
} elseif ( ( nBreakPos > nMaxBreakPos ) && !aUserOptions.allowPunctuationOutsideMargin )
{
OSL_FAIL( "I18N: XBreakIterator::getLineBreak returns position > Max" );
nBreakPos = nMaxBreakPos;
} // Hanging punctuation is the only case that increases nBreakPos and makes // nBreakPos > nMaxBreakPos. It's expected that the hanging punctuation goes over // the border of the object.
}
// BUG in I18N - the japanese dot is in the next line! // !!! Test!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! if ( (nBreakPos + ( bAllowPunctuationOutsideMargin ? 0 : 1 ) ) <= nMaxBreakPos )
{
sal_Unicode cFirstInNextLine = ( (nBreakPos+1) < pNode->Len() ) ? pNode->GetChar( nBreakPos ) : 0; if ( cFirstInNextLine == 12290 )
nBreakPos++;
}
// Whether a separator or not, push the word after the separator through // hyphenation... NMaxBreakPos is the last character that fits into // the line, nBreakPos is the beginning of the word. // There is a problem if the Doc is so narrow that a word is broken // into more than two lines... if ( !bHangingPunctuation && bCanHyphenate && GetHyphenator().is() )
{
i18n::Boundary aBoundary = _xBI->getWordBoundary(
pNode->GetString(), nBreakPos, GetLocale( EditPaM( pNode, nBreakPos ) ), css::i18n::WordType::DICTIONARY_WORD, true);
sal_Int32 nWordStart = nBreakPos;
sal_Int32 nWordEnd = aBoundary.endPos;
DBG_ASSERT( nWordEnd >= nWordStart, "Start >= End?" );
sal_Int32 nWordLen = nWordEnd - nWordStart; if ( ( nWordEnd >= nMaxBreakPos ) && ( nWordLen > 3 ) )
{ // May happen, because getLineBreak may differ from getWordBoundary with DICTIONARY_WORD const OUString aWord = pNode->GetString().copy(nWordStart, nWordLen);
sal_Int32 nMinTrail = nWordEnd-nMaxBreakPos+1; //+1: Before the dickey letter
Reference< XHyphenatedWord > xHyphWord; 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();
// We expect[ed] the two cases: // 1) packen becomes pak-ken // 2) Schiffahrt becomes Schiff-fahrt // In case 1, a character has to be replaced // in case 2 a character is added. // The identification is complicated by long // compound words because the Hyphenator separates // all position of the word. [This is not true for libhyphen.] // "Schiffahrtsbrennesseln" -> "Schifffahrtsbrennnesseln" // We can thus actually not directly connect the index of the // AlternativeWord to aWord. The whole issue will be simplified // by a function in the Hyphenator as soon as AMA builds this in...
sal_Int32 nAltStart = _nWordLen - 1;
sal_Int32 nTxtStart = nAltStart - (aAlt.getLength() - aWord.getLength());
sal_Int32 nTxtEnd = nTxtStart;
sal_Int32 nAltEnd = nAltStart;
// The regions between the nStart and nEnd is the // difference between alternative and original string. while( nTxtEnd < aWord.getLength() && nAltEnd < aAlt.getLength() &&
aWord[nTxtEnd] != aAlt[nAltEnd] )
{
++nTxtEnd;
++nAltEnd;
}
// If a character is added, then we notice it now: if( nAltEnd > nTxtEnd && nAltStart == nAltEnd &&
aWord[ nTxtEnd ] == aAlt[nAltEnd] )
{
++nAltEnd;
++nTxtStart;
++nTxtEnd;
}
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 > rLine.GetStart() && "ImpBreakLines - BreakPos not expected!" ); if ( pNode->GetChar( nBreakPos-1 ) == ' ' )
bCompressBlank = true;
}
if ( bCompressBlank || bHangingPunctuation )
{
TextPortion& rTP = rParaPortion.GetTextPortions()[nEndPortion];
DBG_ASSERT( rTP.GetKind() == PortionKind::TEXT, "BlankRubber: No TextPortion!" );
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();
}
} elseif ( bHyphenated )
{ // A portion for inserting the separator...
TextPortion* pHyphPortion = new TextPortion( 0 );
pHyphPortion->SetKind( PortionKind::HYPHENATOR ); if ( (cAlternateReplChar || cAlternateExtraChar) && bAltFullRight ) // alternation after the break doesn't supported
{
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.setWidth(
rLine.GetCharPosArray()[ nBreakPos-1 - rLine.GetStart() - nAltDelChar ] );
}
// Determine the width of the Hyph-Portion:
SvxFont aFont;
SeekCursor(rParaPortion.GetNode(), nBreakPos, aFont);
aFont.SetPhysFont(*GetRefDevice());
pHyphPortion->SetSize(Size(GetRefDevice()->GetTextWidth(CH_HYPH), GetRefDevice()->GetTextHeight()));
if ( pNode->GetChar(nChar) == ' ' )
{ // Normal latin script.
aPositions.push_back( nChar );
} elseif (nChar > nFirstChar)
{ if (nLastScript == i18n::ScriptType::ASIAN)
{ // Set break position between this and the last character if // the last character is asian script.
aPositions.push_back( nChar-1 );
} elseif (nScript == i18n::ScriptType::ASIAN)
{ // Set break position between a latin script and asian script.
aPositions.push_back( nChar-1 );
}
}
nLastScript = nScript;
}
// Kashidas ? auto nKashidaStart = aPositions.size();
ImpFindKashidas(pNode, nFirstChar, nLastChar, aPositions, nRemainingSpace); auto nKashidas = aPositions.size() - nKashidaStart;
if ( aPositions.empty() ) return;
// If the last character is a blank, it is rejected! // 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) && (!nKashidas))
{
aPositions.pop_back();
sal_Int32 nPortionStart, nPortion;
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 -= 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.adjustSize(-nBlankWidth, 0);
nRemainingSpace += nBlankWidth;
}
rLine.GetCharPosArray()[nLastChar-nFirstChar] -= nBlankWidth;
}
// Mark Kashida positions, so that VCL knows where to insert Kashida and // where to only expand the width. // The underlying array may be reused across updates. Ensure there is no stale data.
rLine.GetKashidaArray().clear(); if (nKashidas)
{
rLine.GetKashidaArray().resize(rLine.GetCharPosArray().size(), false); for (size_t i = 0; i < nKashidas; i++)
{ auto nChar = aPositions[nKashidaStart + 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 (autoconst& nChar : aPositions)
{ if ( nChar < nLastChar )
{
sal_Int32 nPortionStart, nPortion;
nPortion = rParaPortion.GetTextPortions().FindPortion( nChar, nPortionStart, true );
TextPortion& rLastPortion = rParaPortion.GetTextPortions()[ nPortion ];
// The width of the portion:
rLastPortion.adjustSize(nMore4Everyone, 0); if (nSomeExtraSpace)
{
rLastPortion.adjustSize(1, 0);
}
// Every kashida and blank in the block will be given the same amount of extra space. // Greedily reject kashida positions from start-to-end until there is enough room // for all of the remaining kashida. This will push kashida justification away from // the start of the line.
std::reverse(aKashidaArray.begin(), aKashidaArray.end());
std::reverse(aMinKashidaArray.begin(), aMinKashidaArray.end());
auto nGaps = aKashidaArray.size() + rArray.size(); auto nGapSize = nGaps ? (nRemainingSpace / nGaps) : 0; for (size_t i = 0; i < aKashidaArray.size(); ++i)
{ auto nEmRequiredSize = aMinKashidaArray[i]; while (aKashidaArray.size() > i && std::cmp_less(nGapSize, nEmRequiredSize))
{
aMinKashidaArray.pop_back();
aKashidaArray.pop_back();
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;
DBG_ASSERT( pTextPortion, "Position outside the area!" );
if (!pTextPortion) return 0;
DBG_ASSERT( pTextPortion->GetKind() == PortionKind::TEXT, "SplitTextPortion: No TextPortion!" );
sal_Int32 nOverlapp = nTmpPos - nPos;
pTextPortion->SetLen( pTextPortion->GetLen() - nOverlapp );
TextPortion* pNewPortion = new TextPortion( nOverlapp );
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->setWidth(pCurLine->GetCharPosArray()[nPos - pCurLine->GetStart() - 1]);
if ( pTextPortion->GetExtraInfos() && pTextPortion->GetExtraInfos()->bCompressed )
{ // We need the original size from the portion
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(), rParaPortion.GetNode()->GetString(),
nTxtPortionStart, pTextPortion->GetLen(), nullptr );
GetRefDevice()->Pop();
pTextPortion->GetExtraInfos()->nOrgWidth = aSz.Width();
}
} else
pTextPortion->setWidth(-1);
return nSplitPortion;
}
void ImpEditEngine::CreateTextPortions(ParaPortion& rParaPortion, sal_Int32& rStart)
{
sal_Int32 nStartPos = rStart;
ContentNode* pNode = rParaPortion.GetNode();
DBG_ASSERT( pNode->Len(), "CreateTextPortions should not be used for empty paragraphs!" );
for (std::size_t nAttr = 0;; ++nAttr)
{ // Insert Start and End into the Array... // The Insert method does not allow for duplicate values...
EditCharAttrib* pAttrib = GetAttrib(pNode->GetCharAttribs().GetAttribs(), nAttr); if (!pAttrib) break;
aPositions.insert( pAttrib->GetStart() );
aPositions.insert( pAttrib->GetEnd() );
}
aPositions.insert( pNode->Len() );
if (rParaPortion.getScriptTypePosInfos().empty())
InitScriptTypes(GetParaPortions().GetPos(&rParaPortion));
for (const ScriptTypePosInfo& rType : rParaPortion.getScriptTypePosInfos())
aPositions.insert( rType.nStartPos );
for (const WritingDirectionInfo& rWritingDirection : rParaPortion.getWritingDirectionInfos())
aPositions.insert( rWritingDirection.nStartPos );
// From ... Delete: // Unfortunately, the number of text portions does not have to match // aPositions.Count(), since there might be line breaks...
sal_Int32 nPortionStart = 0;
sal_Int32 nInvPortion = 0;
sal_Int32 nP; for ( nP = 0; nP < rParaPortion.GetTextPortions().Count(); nP++ )
{ const TextPortion& rTmpPortion = rParaPortion.GetTextPortions()[nP];
nPortionStart = nPortionStart + rTmpPortion.GetLen(); if ( nPortionStart >= nStartPos )
{
nPortionStart = nPortionStart - rTmpPortion.GetLen();
rStart = nPortionStart;
nInvPortion = nP; break;
}
}
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 - rParaPortion.GetTextPortions()[nInvPortion].GetLen();
}
rParaPortion.GetTextPortions().DeleteFromPortion( nInvPortion );
// A portion may also have been formed by a line break:
aPositions.insert( nPortionStart );
auto i = nInvPos;
++i; while ( i != aPositions.end() )
{
TextPortion* pNew = new TextPortion( (*i++) - *nInvPos++ );
rParaPortion.GetTextPortions().Append(pNew);
}
DBG_ASSERT(rParaPortion.GetTextPortions().Count(), "No Portions?!"); #if OSL_DEBUG_LEVEL > 0
OSL_ENSURE( ParaPortion::DbgCheckTextPortions(rParaPortion), "Portion is broken?" ); #endif
}
void ImpEditEngine::RecalcTextPortion(ParaPortion& rParaPortion, sal_Int32 nStartPos, sal_Int32 nNewChars)
{
DBG_ASSERT(rParaPortion.GetTextPortions().Count(), "No Portions!");
DBG_ASSERT( nNewChars, "RecalcTextPortion with Diff == 0" );
ContentNode* const pNode = rParaPortion.GetNode(); if ( nNewChars > 0 )
{ // If an Attribute begins/ends at nStartPos, then a new portion starts // otherwise the portion is extended at nStartPos. if ( pNode->GetCharAttribs().HasBoundingAttrib( nStartPos ) || IsScriptChange( EditPaM( pNode, nStartPos ) ) )
{
sal_Int32 nNewPortionPos = 0; if ( nStartPos )
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 < rParaPortion.GetTextPortions().Count() ) &&
!rParaPortion.GetTextPortions()[nNewPortionPos].GetLen() )
{
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 );
rParaPortion.GetTextPortions().Insert(nNewPortionPos, pNewPortion);
}
} else
{
sal_Int32 nPortionStart; const sal_Int32 nTP = rParaPortion.GetTextPortions().FindPortion( nStartPos, nPortionStart );
TextPortion& rTP = rParaPortion.GetTextPortions()[ nTP ];
rTP.SetLen( rTP.GetLen() + nNewChars );
rTP.setWidth(-1);
}
} else
{ // Shrink or remove portion if necessary. // Before calling this method it must be ensured that no portions were // in the deleted area!
// There must be no portions extending into the area or portions starting in // the area, so it must be: // nStartPos <= nPos <= nStartPos - nNewChars(neg.)
sal_Int32 nPortion = 0;
sal_Int32 nPos = 0;
sal_Int32 nEnd = nStartPos-nNewChars;
sal_Int32 nPortions = rParaPortion.GetTextPortions().Count();
TextPortion* pTP = nullptr; for ( nPortion = 0; nPortion < nPortions; nPortion++ )
{
pTP = &rParaPortion.GetTextPortions()[ nPortion ]; if ( ( nPos+pTP->GetLen() ) > nStartPos )
{
DBG_ASSERT( nPos <= nStartPos, "Wrong Start!" );
DBG_ASSERT( nPos+pTP->GetLen() >= nEnd, "Wrong End!" ); break;
}
nPos = nPos + pTP->GetLen();
}
assert( pTP && "RecalcTextPortion: Portion not found" ); if ( ( nPos == nStartPos ) && ( (nPos+pTP->GetLen()) == nEnd ) )
{ // Remove portion;
PortionKind nType = pTP->GetKind();
rParaPortion.GetTextPortions().Remove( nPortion ); if ( nType == PortionKind::LINEBREAK )
{
TextPortion& rNext = rParaPortion.GetTextPortions()[ nPortion ]; if ( !rNext.GetLen() )
{ // Remove dummy portion
rParaPortion.GetTextPortions().Remove( nPortion );
}
}
} else
{
DBG_ASSERT( pTP->GetLen() > (-nNewChars), "Portion too small to shrink! ");
pTP->SetLen( pTP->GetLen() + nNewChars );
}
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 = &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 = rParaPortion.GetTextPortions()[nLastPortion - 1];
DBG_ASSERT( rPrev.GetKind() == PortionKind::TEXT, "Portion?!" );
rPrev.SetLen( rPrev.GetLen() + pTP->GetLen() );
rPrev.setWidth(-1);
}
rParaPortion.GetTextPortions().Remove( nLastPortion );
}
}
} #if OSL_DEBUG_LEVEL > 0
OSL_ENSURE( ParaPortion::DbgCheckTextPortions(rParaPortion), "Portions are broken?" ); #endif
}
void ImpEditEngine::SeekCursor( ContentNode* pNode, sal_Int32 nPos, SvxFont& rFont, OutputDevice* pOut )
{ // It was planned, SeekCursor( nStartPos, nEndPos,... ), so that it would // only be searched anew at the StartPosition. // Problem: There would be two lists to consider/handle: // OrderedByStart,OrderedByEnd.
/* * Scan through char attributes of pNode
*/ if (maStatus.UseCharAttribs())
{
CharAttribList::AttribsType& rAttribs = pNode->GetCharAttribs().GetAttribs();
size_t nAttr = 0;
EditCharAttrib* pAttrib = GetAttrib(rAttribs, nAttr); while ( pAttrib && ( pAttrib->GetStart() <= nPos ) )
{ // when seeking, ignore attributes which start there! Empty attributes // are considered (used) as these are just set. But do not use empty // attributes: When just set and empty => no effect on font // In a blank paragraph, set characters take effect immediately. if ( ( pAttrib->Which() != 0 ) &&
( ( ( pAttrib->GetStart() < nPos ) && ( pAttrib->GetEnd() >= nPos ) )
|| ( !pNode->Len() ) ) )
{
DBG_ASSERT( ( pAttrib->Which() >= EE_CHAR_START ) && ( pAttrib->Which() <= EE_FEATURE_END ), "Invalid Attribute in Seek() " ); if ( IsScriptItemValid( pAttrib->Which(), nScriptTypeI18N ) )
{
pAttrib->SetFont( rFont, pOut ); // #i1550# hard color attrib should win over text color from field if ( pAttrib->Which() == EE_FEATURE_FIELD )
{ // 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 );
}
} if ( pAttrib->Which() == EE_CHAR_FONTWIDTH )
nRelWidth = static_cast<const SvxCharScaleWidthItem*>(pAttrib->GetItem())->GetValue(); if ( pAttrib->Which() == EE_CHAR_LANGUAGE_CJK )
pCJKLanguageItem = static_cast<const SvxLanguageItem*>( pAttrib->GetItem() );
}
pAttrib = GetAttrib( rAttribs, ++nAttr );
}
}
if ( !pCJKLanguageItem )
pCJKLanguageItem = &pNode->GetContentAttribs().GetItem( EE_CHAR_LANGUAGE_CJK );
// tdf#160401/#i78474# small caps do not exist in CTL fonts, so turn that off here where we know the script type if (rFont.IsCapital() && nScriptTypeI18N == i18n::ScriptType::COMPLEX)
rFont.SetCaseMap(SvxCaseMap::NotMapped);
if (maStatus.DoNotUseColors())
{
rFont.SetColor( /* rColorItem.GetValue() */ COL_BLACK );
}
if (maStatus.DoStretch() || ( nRelWidth != 100 ))
{ // For the current Output device, because otherwise if RefDev=Printer its looks // ugly on the screen!
OutputDevice* pDev = pOut ? pOut : GetRefDevice();
rFont.SetPhysFont(*pDev);
FontMetric aMetric( pDev->GetFontMetric() );
// before forcing nPropr to 100%, calculate a new escapement relative to this fake size.
sal_uInt8 nPropr = rFont.GetPropr();
sal_Int16 nEsc = rFont.GetEscapement(); if ( nPropr && nEsc && nPropr != 100 && abs(nEsc) != DFLT_ESC_AUTO_SUPER )
rFont.SetEscapement( 100.0/nPropr * nEsc );
// Set the font as we want it to look like & reset the Propr attribute // so that it is not counted twice.
Size aRealSz( aMetric.GetFontSize() );
rFont.SetPropr( 100 );
// Also the Kerning: (long due to handle Interim results)
tools::Long nKerning = rFont.GetFixKerning(); /* The consideration was: If negative kerning, but StretchX = 200 => Do not double the kerning, thus pull the letters closer together --------------------------- Kern StretchX =>Kern --------------------------- >0 <100 < (Proportional) <0 <100 < (Proportional) >0 >100 > (Proportional) <0 >100 < (The amount, thus disproportional)
*/ if (nKerning < 0 && fFontX > 1.0)
{ // disproportional
nKerning = basegfx::fround(nKerning / fFontX);
} elseif ( nKerning )
{ // Proportional
nKerning = basegfx::fround(nKerning * fFontX);
}
rFont.SetFixKerning( static_cast<short>(nKerning) );
}
}
} if ( nRelWidth != 100 )
{
aRealSz.setWidth( aRealSz.Width() * nRelWidth );
aRealSz.setWidth( aRealSz.Width() / 100 );
}
rFont.SetFontSize( aRealSz ); // Font is not restored...
}
if ( ( ( rFont.GetColor() == COL_AUTO ) || ( IsForceAutoColor() ) ) && pOut )
{ // #i75566# Do not use AutoColor when printing OR Pdf export constbool bPrinting(OUTDEV_PRINTER == pOut->GetOutDevType()); constbool bPDFExporting(OUTDEV_PDF == pOut->GetOutDevType());
if ( IsAutoColorEnabled() && !bPrinting && !bPDFExporting)
{ // Never use WindowTextColor on the printer
rFont.SetColor( GetAutoColor() );
} else
{ if ( ( GetBackgroundColor() != COL_AUTO ) && GetBackgroundColor().IsDark() )
rFont.SetColor( COL_WHITE ); else
rFont.SetColor( COL_BLACK );
}
}
if ( IsFixedCellHeight() )
{
nAscent = sal::static_int_cast< sal_uInt16 >( rFont.GetFontHeight() );
nDescent= sal::static_int_cast< sal_uInt16 >( ImplCalculateFontIndependentLineSpacing( rFont.GetFontHeight() ) - nAscent );
} else
{
sal_uInt16 nIntLeading = ( aMetric.GetInternalLeading() > 0 ) ? static_cast<sal_uInt16>(aMetric.GetInternalLeading()) : 0; // Fonts without leading cause problems if ( ( nIntLeading == 0 ) && (mpRefDev->GetOutDevType() == OUTDEV_PRINTER))
{ // Let's see what Leading one gets on the screen
VclPtr<VirtualDevice> pVDev = GetVirtualDevice(mpRefDev->GetMapMode(), mpRefDev->GetDrawMode());
rFont.SetPhysFont(*pVDev);
aMetric = pVDev->GetFontMetric();
// This is so that the Leading does not count itself out again, // if the whole line has the font, nTmpLeading.
nAscent = static_cast<sal_uInt16>(aMetric.GetAscent());
nDescent = static_cast<sal_uInt16>(aMetric.GetDescent());
}
} if ( nAscent > rCurMetrics.nMaxAscent )
rCurMetrics.nMaxAscent = nAscent; if ( nDescent > rCurMetrics.nMaxDescent )
rCurMetrics.nMaxDescent= nDescent; // Special treatment of high/low: if ( !rFont.GetEscapement() ) return;
// Now in consideration of Escape/Propr // possibly enlarge Ascent or Descent short nDiff = static_cast<short>(rFont.GetFontSize().Height()*rFont.GetEscapement()/100); if ( rFont.GetEscapement() > 0 )
{
nAscent = static_cast<sal_uInt16>(static_cast<tools::Long>(nAscent)*nPropr/100 + nDiff); if ( nAscent > rCurMetrics.nMaxAscent )
rCurMetrics.nMaxAscent = nAscent;
} else// has to be < 0
{
nDescent = static_cast<sal_uInt16>(static_cast<tools::Long>(nDescent)*nPropr/100 - nDiff); if ( nDescent > rCurMetrics.nMaxDescent )
rCurMetrics.nMaxDescent= nDescent;
}
}
// Returns the resulting shift for the point; allows to apply the same shift to other points
Point ImpEditEngine::MoveToNextLine(
Point& rMovePos, // [in, out] Point that will move to the next line
tools::Long nLineHeight, // [in] Y-direction move distance (direction-aware)
sal_Int16& rColumn, // [in, out] current column number
Point aOrigin, // [in] Origin point to calculate limits and initial Y position in a new column
tools::Long* pnHeightNeededToNotWrap // On column wrap, returns how much more height is needed
) const
{ const Point aOld = rMovePos;
// 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 maPaperSize // and height of mnCurTextHeight
Point aOtherCorner = aOrigin;
adjustXDirectionAware(aOtherCorner, getWidthDirectionAware(maPaperSize));
adjustYDirectionAware(aOtherCorner, mnCurTextHeight);
tools::Long nNeeded
= getYOverflowDirectionAware(rMovePos, tools::Rectangle::Normalize(aOrigin, aOtherCorner)); if (pnHeightNeededToNotWrap)
*pnHeightNeededToNotWrap = nNeeded; if (nNeeded && rColumn < mnColumns)
{
++rColumn; // If we didn't fit into the last column, indicate that only by setting the column number // to the total number of columns; do not adjust if (rColumn < mnColumns)
{ // Set Y position of the point to that of aOrigin
setYDirectionAwareFrom(rMovePos, aOrigin); // 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(maPaperSize) + mnColumnSpacing);
}
}
return rMovePos - aOld;
}
void ImpEditEngine::Draw( OutputDevice& rOutDev, const Point& rStartPos, Degree10 nOrientation )
{ // Create with 2 points, as with positive points it will end up with // LONGMAX as Size, Bottom and Right in the range > LONGMAX.
tools::Rectangle aBigRect( -0x3FFFFFFF, -0x3FFFFFFF, 0x3FFFFFFF, 0x3FFFFFFF ); if( rOutDev.GetConnectMetaFile() )
rOutDev.Push();
Point aStartPos( rStartPos ); if ( IsEffectivelyVertical() )
{
aStartPos.AdjustX(GetPaperSize().Width() );
rStartPos.RotateAround(aStartPos, nOrientation);
}
Paint(rOutDev, aBigRect, aStartPos, nOrientation); if (rOutDev.GetConnectMetaFile())
rOutDev.Pop();
}
// Align to the pixel boundary, so that it becomes exactly the same // as Paint ()
tools::Rectangle aOutRect( rOutDev.LogicToPixel( rOutRect ) );
aOutRect = rOutDev.PixelToLogic( aOutRect );
// In the case of rotated text is aStartPos considered TopLeft because // other information is missing, and since the whole object is shown anyway // un-scrolled. // The rectangle is infinite. const Point aOrigin( aStartPos );
// #110496# Added some more optional metafile comments. This // change: factored out some duplicated code.
GDIMetaFile* pMtf = rOutDev.GetConnectMetaFile(); constbool bMetafileValid( pMtf != nullptr );
// Why not just also call when stripping portions? This will give the correct values // and needs no position corrections in OutlinerEditEng::DrawingText which tries to call // PaintBullet correctly; exactly what GetEditEnginePtr()->PaintingFirstLine // does, too. No change for not-layouting (painting). if(0 == nLine) // && !bStripOnly)
{
Point aLineStart(aStartPos);
adjustYDirectionAware(aLineStart, -nLineHeight);
GetEditEnginePtr()->PaintingFirstLine(nParaPortion, aLineStart, aOrigin, nOrientation, rOutDev,
rDrawPortion, rDrawBullet);
// Remember whether a bullet was painted. const SfxBoolItem& rBulletState = mpEditEngine->GetParaAttrib(nParaPortion, EE_PARA_BULLETSTATE);
bPaintBullet = rBulletState.GetValue();
}
// Paint control characters (#i55716#) /* XXX: Given that there's special handling * only for some specific characters * (U+200B ZERO WIDTH SPACE and U+2060 WORD * JOINER) it is assumed to be not relevant
* for MarkUrlFields(). */ if (maStatus.MarkNonUrlFields())
{
sal_Int32 nTmpIdx; const sal_Int32 nTmpEnd = nTextStart + rTextPortion.GetLen();
aTmpFont.SetEscapement( nOldEscapement );
aTmpFont.SetPropr( nOldPropr );
aTmpFont.SetPhysFont(rOutDev);
}
}
}
}
} elseif ( rTextPortion.GetKind() == PortionKind::FIELD )
{ 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(); //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 (rDrawPortion && !bParsingFields && pExtraInfo && !pExtraInfo->lineBreaksList.empty())
{
bParsingFields = true;
itSubLines = pExtraInfo->lineBreaksList.begin();
}
if( bParsingFields )
{ if( itSubLines != pExtraInfo->lineBreaksList.begin() )
{ // only use GetMaxAscent(), pLine->GetHeight() will not // proceed as needed (see PortionKind::TEXT above and nAdvanceY) // what will lead to a compressed look with multiple lines const sal_uInt16 nMaxAscent(pLine->GetMaxAscent());
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
} elseif (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];
}
}
}
}
Point aOutPos( aTmpPos );
Point aRedLineTmpPos = aTmpPos; // In RTL portions spell markup pos should be at the start of the // first chara as well. That is on the right end of the portion if (rTextPortion.IsRightToLeft())
aRedLineTmpPos.AdjustX(rTextPortion.GetSize().Width() );
if ( rDrawPortion )
{
EEngineData::WrongSpellVector aWrongSpellVector;
// 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(rParaPortion.GetNode(), nIndex + 1)));
// get Overline color (from ((const SvxOverlineItem*)GetItem())->GetColor() in // consequence, but also already set at rOutDev) const Color aOverlineColor(rOutDev.GetOverlineColor());
// get TextLine color (from ((const SvxUnderlineItem*)GetItem())->GetColor() in // consequence, but also already set at rOutDev) const Color aTextLineColor(rOutDev.GetTextLineColor());
// Unicode code points conversion according to ctl text numeral setting
aText = convertDigits(aText, nTextStart, nTextLen,
ImplCalcDigitLang(aTmpFont.GetLanguage()));
// StripPortions() data callback const DrawPortionInfo aInfo(
aOutPos, aText, nTextStart, nTextLen, pDXArray, pKashidaArray,
aTmpFont, nParaPortion, rTextPortion.GetRightToLeftLevel(),
!aWrongSpellVector.empty() ? &aWrongSpellVector : nullptr,
pFieldData,
bEndOfLine, bEndOfParagraph, false, // support for EOL/EOP TEXT comments
&aLocale,
aOverlineColor,
aTextLineColor);
rDrawPortion(aInfo);
// #108052# remember that EOP is written already for this ParaPortion if(bEndOfParagraph)
{
bEndOfParagraphWritten = true;
}
} else
{ short nEsc = aTmpFont.GetEscapement(); if ( nOrientation )
{ // In case of high/low do it yourself: if ( aTmpFont.GetEscapement() )
{
tools::Long nDiff = aTmpFont.GetFontSize().Height() * aTmpFont.GetEscapement() / 100L;
adjustYDirectionAware(aOutPos, -nDiff);
aRedLineTmpPos = aOutPos;
aTmpFont.SetEscapement( 0 );
}
if ( bDrawFrame )
{
Point aTopLeft( aTmpPos );
aTopLeft.AdjustY( -(pLine->GetMaxAscent()) ); if ( nOrientation )
aOrigin.RotateAround(aTopLeft, nOrientation);
tools::Rectangle aRect( aTopLeft, rTextPortion.GetSize() );
rOutDev.DrawRect( aRect );
}
// PDF export: if (pPDFExtOutDevData)
{ if (auto pUrlField = dynamic_cast<const SvxURLField*>(pFieldData))
{
Point aTopLeft(aTmpPos);
aTopLeft.AdjustY(-(pLine->GetMaxAscent()));
if ( rTextPortion.GetKind() == PortionKind::FIELD )
{ // add a meta file comment if we record to a metafile if( bMetafileValid )
{ const EditCharAttrib* pAttr = rParaPortion.GetNode()->GetCharAttribs().FindFeature(nIndex);
assert( pAttr && "Field not found" );
const SvxFieldItem* pFieldItem = dynamic_cast<const SvxFieldItem*>(pAttr->GetItem());
DBG_ASSERT( pFieldItem != nullptr, "Wrong type of field!" );
// #108052# Safer way for #i108052# and #i118881#: If for the current ParaPortion // EOP is not written, do it now. This will be safer than before. It has shown // that the reason for #i108052# was fixed/removed again, so this is a try to fix // the number of paragraphs (and counting empty ones) now independent from the // changes in EditEngine behaviour. if(!bEndOfParagraphWritten && !bPaintBullet && rDrawPortion)
{ const Color aOverlineColor(rOutDev.GetOverlineColor()); const Color aTextLineColor(rOutDev.GetTextLineColor());
const Point aStartPos(CalculateTextPaintStartPosition(*pView));
// If Doc-width < Output Area,Width and not wrapped fields, // the fields usually protrude if > line. // (Not at the top, since there the Doc-width from formatting is already // there) if ( !IsEffectivelyVertical() && ( pView->GetOutputArea().GetWidth() > GetPaperSize().Width() ) )
{
tools::Long nMaxX = pView->GetOutputArea().Left() + GetPaperSize().Width(); if ( aClipRect.Left() > nMaxX ) return; if ( aClipRect.Right() > nMaxX )
aClipRect.SetRight( nMaxX );
}
// When switching from true to false, all selections were visible, // => paint over // the other hand, were all invisible => paint // 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.
mbUpdateLayout = bUp; if ( mbUpdateLayout && ( mbChanged || bForceUpdate ) )
FormatAndLayout( pCurView ); return bPrevUpdateLayout;
}
if ( !bShow )
{ // Mark as deleted, so that no selection will end or begin at // this paragraph...
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 (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: // ( and correspondingly in between as well...) if ( pCurView && IsUpdateLayout() )
{ // in this case one can redraw directly without invalidating the // Portions
sal_Int32 nFirstPortion = std::min( static_cast<sal_Int32>(aOldPositions.Min()), nNewPos );
sal_Int32 nLastPortion = std::max( static_cast<sal_Int32>(aOldPositions.Max()), nNewPos );
UpdateViews( pCurView );
}
} else
{ // redraw from the upper invalid position
sal_Int32 nFirstInvPara = std::min( static_cast<sal_Int32>(aOldPositions.Min()), nNewPos );
InvalidateFromParagraph( nFirstInvPara );
} return aSel;
}
void ImpEditEngine::InvalidateFromParagraph( sal_Int32 nFirstInvPara )
{ // The following paragraphs are not invalidated, since ResetHeight() // => size change => all the following are re-issued anyway.
void ImpEditEngine::CallStatusHdl()
{ 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( maStatus );
maStatus.Clear();
maStatusHdlLink.Call( aTmpStatus );
maStatusTimer.Stop(); // If called by hand...
}
}
for (sal_Int32 i = 0; i < nParaCount; ++i)
{ if (GetVerJustification(i) != SvxCellVerJustify::Block) // All paragraphs must have the block justification set. return 0;
// Shift the text to the right for the asian layout mode. if (IsEffectivelyVertical())
adjustYDirectionAware(rStartPos, -nTotalSpace);
return nTotalSpace / (nTotalLineCount-1);
}
void ImpEditEngine::InsertParagraph( sal_Int32 nPara, const EditTextObject& rTxtObj, bool bAppend )
{ if ( nPara > maEditDoc.Count() )
{
SAL_WARN_IF( nPara != EE_PARA_MAX, "editeng", "Paragraph number too large, but not EE_PARA_MAX!" );
nPara = maEditDoc.Count();
}
UndoActionStart(EDITUNDO_INSERT);
// No Undo compounding needed.
EditPaM aPaM(InsertParagraph(nPara)); // When InsertParagraph from the outside, no hard attributes // should be taken over!
RemoveCharAttribs(nPara);
InsertText(rTxtObj, EditSelection(aPaM, aPaM));
if ( bAppend && nPara )
ConnectContents(nPara - 1, /*bBackwards=*/false);
UndoActionEnd();
if (IsUpdateLayout())
FormatAndLayout();
}
void ImpEditEngine::InsertParagraph(sal_Int32 nPara, const OUString& rTxt)
{ if ( nPara > maEditDoc.Count() )
{
SAL_WARN_IF( nPara != EE_PARA_MAX, "editeng", "Paragraph number too large, but not EE_PARA_MAX!" );
nPara = maEditDoc.Count();
}
UndoActionStart(EDITUNDO_INSERT);
EditPaM aPaM(InsertParagraph(nPara)); // When InsertParagraph from the outside, no hard attributes // should be taken over!
RemoveCharAttribs(nPara);
UndoActionEnd();
ImpInsertText(EditSelection(aPaM, aPaM), rTxt); if (IsUpdateLayout())
FormatAndLayout();
}
if ( IsInUndo() )
IdleFormatAndLayout( pCurView ); else
{ if (bCalledFromUndo)
{ // in order to make bullet points that have had their styles changed, redraw themselves for (auto& pParaPortion : GetParaPortions())
pParaPortion->MarkInvalid(0, 0);
}
FormatDoc();
UpdateViews( pCurView );
}
if (pNode)
{ // get index of paragraph
sal_Int32 nPara = GetEditDoc().GetPos( pNode );
DBG_ASSERT( nPara < EE_PARA_MAX, "node not found in array" ); if (nPara < EE_PARA_MAX)
{ // the called function may be overridden by an OutlinerEditEng // object to provide // access to the SvxNumberFormat of the Outliner. // The EditEngine implementation will just return 0.
pRes = mpEditEngine->GetNumberFormat( nPara );
}
}
// if no number format was found we have no Outliner or the numbering level // within the Outliner is -1 which means no number format should be applied. // Thus the default values to be returned are 0.
sal_Int32 nSpaceBefore = 0;
sal_Int32 nMinLabelWidth = 0;
// select a representative text language for the digit type according to the // text numeral setting:
LanguageType ImpEditEngine::ImplCalcDigitLang(LanguageType eCurLang)
{ if (comphelper::IsFuzzing()) return LANGUAGE_ENGLISH_US;
// #114278# Also setting up digit language from Svt options // (cannot reliably inherit the outdev's setting)
// Either sets the digit mode at the output device void ImpEditEngine::ImplInitDigitMode(OutputDevice& rOutDev, LanguageType eCurLang)
{
rOutDev.SetDigitLanguage(ImplCalcDigitLang(eCurLang));
}
void ImpEditEngine::ImplInitLayoutMode(OutputDevice& rOutDev, sal_Int32 nPara, sal_Int32 nIndex)
{ bool bCTL = false; bool bR2L = false; if ( nIndex == -1 )
{
bCTL = HasScriptType( nPara, i18n::ScriptType::COMPLEX );
bR2L = IsRightToLeft( nPara );
} else
{
ContentNode* pNode = GetEditDoc().GetObject( nPara ); short nScriptType = GetI18NScriptType( EditPaM( pNode, nIndex+1 ) );
bCTL = nScriptType == i18n::ScriptType::COMPLEX; // this change was discussed in issue 37190
bR2L = (GetRightToLeft( nPara, nIndex + 1) % 2) != 0; // it also works for issue 55927
}
// We always use the left position for DrawText()
nLayoutMode &= ~vcl::text::ComplexTextLayoutFlags::BiDiRtl;
if ( !bCTL && !bR2L)
{ // No Bidi checking necessary
nLayoutMode |= vcl::text::ComplexTextLayoutFlags::BiDiStrong;
} else
{ // Bidi checking necessary // Don't use BIDI_STRONG, VCL must do some checks.
nLayoutMode &= ~vcl::text::ComplexTextLayoutFlags::BiDiStrong;
if ( bR2L )
nLayoutMode |= vcl::text::ComplexTextLayoutFlags::BiDiRtl|vcl::text::ComplexTextLayoutFlags::TextOriginLeft;
}
rOutDev.SetLayoutMode( nLayoutMode );
// #114278# Also setting up digit language from Svt options // (cannot reliably inherit the outdev's setting)
LanguageType eLang = Application::GetSettings().GetLanguageTag().getLanguageType();
ImplInitDigitMode(rOutDev, eLang);
}
// Special handling for rightpunctuation: For the 'compression' we must // start the output before the normal char position... if ( bManipulateDXArray && ( pTextPortion->GetLen() > 1 ) )
{ if ( !pTextPortion->GetExtraInfos()->pOrgDXArray )
pTextPortion->GetExtraInfos()->SaveOrgDXArray( pDXArray, pTextPortion->GetLen()-1 );
if ( nType == AsianCompressionFlags::PunctuationRight )
{ // If it's the first char, I must handle it in Paint()... if ( n )
{ // -1: No entry for the last character for ( sal_Int32 i = n-1; i < (nPortionLen-1); i++ )
pDXArray[i] -= nCompress;
} else
{
pTextPortion->GetExtraInfos()->bFirstCharIsRightPunctuation = true;
pTextPortion->GetExtraInfos()->nPortionOffsetX = -nCompress;
}
} else
{ // -1: No entry for the last character for ( sal_Int32 i = n; i < (nPortionLen-1); i++ )
pDXArray[i] -= nCompress;
}
}
}
}
}
// Like UpdateOverflowingParaNum but for each line in the first // overflowing paragraph. 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 = rParaPortion.GetLines()[nLine];
nLH = aLine.GetHeight();
nY += nLH;
// Debugging output if (nLine == 0)
{
SAL_INFO("editeng.chaining", "[CHAINING] First line has height " << nLH);
}
if ( nY > nPaperHeight ) // found first line overflowing
{
mnOverflowingLine = nLine;
SAL_INFO("editeng.chaining", "[CHAINING] Setting first overflowing -Line- to: " << nLine); return;
}
}
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.