/* -*- 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 .
*/
// Get the effective number format, including formula result types. // This assumes that a formula cell has already been calculated.
sal_uLong GetResultValueFormat() const { return nValueFormat;}
// ScOutputData::LayoutStrings() usually triggers a number of calls that require // to lay out the text, which is relatively slow, so cache that operation. const SalLayoutGlyphs* GetLayoutGlyphs(const OUString& rString) const
{ return SalLayoutGlyphsCache::self()->GetLayoutGlyphs(pOutput->pFmtDevice, rString);
}
private:
tools::Long GetMaxDigitWidth(); // in logic units
tools::Long GetSignWidth();
tools::Long GetDotWidth();
tools::Long GetExpWidth(); void TextChanged();
};
// #i31843# "repeat" with "line breaks" is treated as default alignment (but rotation is still disabled) if ( bLineBreak )
eAttrHorJust = SvxCellHorJustify::Standard;
}
if (pOutput->mbSyntaxMode)
pOutput->SetSyntaxColor(&aFont, rCell);
// There is no cell attribute for kerning, default is kerning OFF, all // kerning is stored at an EditText object that is drawn using EditEngine. // See also matching kerning cases in ScColumn::GetNeededSize and // ScColumn::GetOptimalColWidth.
aFont.SetKerning(FontKerning::NONE);
// if there is the leading 0 on a printer device, we have problems // -> take metric from the screen (as for EditEngine!) if ( pFmtDevice->GetOutDevType() == OUTDEV_PRINTER && aMetric.GetInternalLeading() == 0 )
{
OutputDevice* pDefaultDev = Application::GetDefaultDevice();
MapMode aOld = pDefaultDev->GetMapMode();
pDefaultDev->SetMapMode( pFmtDevice->GetMapMode() );
aMetric = pDefaultDev->GetFontMetric( aFont );
pDefaultDev->SetMapMode( aOld );
}
// Measuring a string containing a single copy of the repeat char is inaccurate. // To increase accuracy, start with a representative sample of a padding sequence.
constexpr sal_Int32 nSampleSize = 20;
OUStringBuffer aFill(nSampleSize);
comphelper::string::padToLength(aFill, nSampleSize, nRepeatChar);
// Intentionally truncate to round toward zero auto nCharWidth = static_cast<tools::Long>(nAvgCharWidth); if ( nCharWidth < 1 || (bPixelToLogic && nCharWidth < pOutput->mpRefDevice->PixelToLogic(Size(1,0)).Width()) ) return;
// Are there restrictions on the cell type we should filter out here ?
tools::Long nTextWidth = aTextSize.Width(); if ( bPixelToLogic )
{
nColWidth = pOutput->mpRefDevice->PixelToLogic(Size(nColWidth,0)).Width();
nTextWidth = pOutput->mpRefDevice->PixelToLogic(Size(nTextWidth,0)).Width();
}
// Intentionally truncate to round toward zero auto nCharsToInsert = static_cast<sal_Int32>(static_cast<double>(nSpaceToFill) / nAvgCharWidth);
aFill.ensureCapacity(nCharsToInsert);
comphelper::string::padToLength(aFill, nCharsToInsert, nRepeatChar);
aString = aString.replaceAt( nRepeatPos, 0, aFill );
TextChanged();
}
bool ScDrawStringsVars::SetTextToWidthOrHash( ScRefCellValue& rCell, tools::LongnWidth )
{ // #i113045# do the single-character width calculations in logic units if (bPixelToLogic)
nWidth = pOutput->mpRefDevice->PixelToLogic(Size(nWidth,0)).Width();
CellType eType = rCell.getType(); if (eType != CELLTYPE_VALUE && eType != CELLTYPE_FORMULA) // must be a value or formula cell. returnfalse;
if (eType == CELLTYPE_FORMULA)
{
ScFormulaCell* pFCell = rCell.getFormula(); if (pFCell->GetErrCode() != FormulaError::NONE || pOutput->mbShowFormulas)
{
SetHashText(); // If the error string doesn't fit, always use "###". Also for "display formulas" (#i116691#) returntrue;
} // If it's formula, the result must be a value. if (!pFCell->IsValue()) returnfalse;
}
sal_uLong nFormat = GetResultValueFormat(); if ((nFormat % SV_COUNTRY_LANGUAGE_OFFSET) != 0)
{ // Not 'General' number format. Set hash text and bail out.
SetHashText(); returntrue;
}
double fVal = rCell.getValue();
const SvNumberformat* pNumFormat = pOutput->mpDoc->GetFormatTable()->GetEntry(nFormat); if (!pNumFormat) returnfalse;
tools::Long nMaxDigit = GetMaxDigitWidth(); if (!nMaxDigit) returnfalse;
// #i112250# A small value might be formatted as "0" when only counting the digits, // but fit into the column when considering the smaller width of the decimal separator. if (aString == "0" && fVal != 0.0)
nDecimalCount = 1;
if (nDecimalCount)
nWidth += (nMaxDigit - GetDotWidth()) * nDecimalCount; if (nSignCount)
nWidth += (nMaxDigit - GetSignWidth()) * nSignCount; if (nExpCount)
nWidth += (nMaxDigit - GetExpWidth()) * nExpCount;
if (nDecimalCount || nSignCount || nExpCount)
{ // Re-calculate.
nNumDigits = static_cast<sal_uInt16>(nWidth / nMaxDigit);
OUString sTempOut(aString); if (!pNumFormat->GetOutputString(fVal, nNumDigits, sTempOut, pOutput->mpDoc->GetFormatTable()->GetNatNum()))
{
aString = sTempOut; // Failed to get output string. Bail out. returnfalse;
}
aString = sTempOut;
}
tools::Long nActualTextWidth = GetFmtTextWidth(aString); if (nActualTextWidth > nWidth)
{ // Even after the decimal adjustment the text doesn't fit. Give up.
SetHashText(); returntrue;
}
TextChanged();
maLastCell.clear(); // #i113022# equal cell and format in another column may give different string returnfalse;
}
maLastCell.clear(); // the same text may fit in the next cell
}
tools::Long ScDrawStringsVars::GetMaxDigitWidth()
{ if (nMaxDigitWidth > 0) return nMaxDigitWidth;
for (char i = 0; i < 10; ++i)
{ char cDigit = '0' + i; // Do not cache this with GetFmtTextWidth(), nMaxDigitWidth is already cached.
tools::Long n = pOutput->pFmtDevice->GetTextWidth(OUString(cDigit));
nMaxDigitWidth = ::std::max(nMaxDigitWidth, n);
} return nMaxDigitWidth;
}
tools::Long ScDrawStringsVars::GetSignWidth()
{ if (nSignWidth > 0) return nSignWidth;
bool ScDrawStringsVars::HasEditCharacters() const
{ for (sal_Int32 nIdx = 0; nIdx < aString.getLength(); ++nIdx)
{ switch(aString[nIdx])
{ case CHAR_NBSP: // tdf#122676: Ignore CHAR_NBSP (this is thousand separator in any number) // if repeat character is set if (nRepeatPos < 0) returntrue; break; case CHAR_SHY: case CHAR_ZWSP: case CHAR_LRM: case CHAR_RLM: case CHAR_NBHY: case CHAR_WJ: returntrue; default: break;
}
}
returnfalse;
}
double ScOutputData::GetStretch() const
{ if ( mpRefDevice->IsMapModeEnabled() )
{ // If a non-trivial MapMode is set, its scale is now already // taken into account in the OutputDevice's font handling // (OutputDevice::ImplNewFont, see #95414#). // The old handling below is only needed for pixel output. return 1.0;
}
// calculation in double is faster than Fraction multiplication // and doesn't overflow
if ( mpRefDevice == pFmtDevice )
{
MapMode aOld = mpRefDevice->GetMapMode(); returnstatic_cast<double>(aOld.GetScaleY()) / static_cast<double>(aOld.GetScaleX()) * static_cast<double>(aZoomY) / static_cast<double>(aZoomX);
} else
{ // when formatting for printer, device map mode has already been taken care of returnstatic_cast<double>(aZoomY) / static_cast<double>(aZoomX);
}
}
if ( !bEmpty && ( nX < nX1 || nX > nX2 || !pThisRowInfo ) )
{ // for the range nX1..nX2 in RowInfo, cell protection attribute is already evaluated // into bEmptyCellText in ScDocument::FillInfo / lcl_HidePrint (printfun)
bool ScOutputData::IsAvailable( SCCOL nX, SCROW nY )
{ // apply the same logic here as in DrawStrings/DrawEdit: // Stop at non-empty or merged or overlapped cell, // where a note is empty as well as a cell that's hidden by protection settings
// nX, nArrY: loop variables from DrawStrings / DrawEdit // nPosX, nPosY: corresponding positions for nX, nArrY // nCellX, nCellY: position of the cell that contains the text // nNeeded: Text width, including margin // rPattern: cell format at nCellX, nCellY // nHorJustify: horizontal alignment (visual) to determine which cells to use for long strings // bCellIsValue: if set, don't extend into empty cells // bBreak: if set, don't extend, and don't set clip marks (but rLeftClip/rRightClip is set) // bOverwrite: if set, also extend into non-empty cells (for rotated text) // rParam output: various area parameters.
void ScOutputData::GetOutputArea( SCCOL nX, SCSIZE nArrY, tools::Long nPosX, tools::LongnPosY,
SCCOL nCellX, SCROW nCellY, tools::Long nNeeded, const ScPatternAttr& rPattern,
sal_uInt16 nHorJustify, bool bCellIsValue, bool bBreak, bool bOverwrite,
OutputAreaParam& rParam )
{ // rThisRowInfo may be for a different row than nCellY, is still used for clip marks
RowInfo& rThisRowInfo = pRowInfo[nArrY];
// nLeftMissing, nRightMissing are logical, eHorJust values are visual if ( bLayoutRTL )
::std::swap( nLeftMissing, nRightMissing );
SCCOL nRightX = nCellX;
SCCOL nLeftX = nCellX; if ( !bMerged && !bCellIsValue && !bBreak )
{ // look for empty cells into which the text can be extended
bool beginsWithRTLCharacter(const OUString& rStr)
{ if (rStr.isEmpty()) returnfalse;
switch (ScGlobal::getCharClass().getCharacterDirection(rStr, 0))
{ case i18n::DirectionProperty_RIGHT_TO_LEFT: case i18n::DirectionProperty_RIGHT_TO_LEFT_ARABIC: case i18n::DirectionProperty_RIGHT_TO_LEFT_EMBEDDING: case i18n::DirectionProperty_RIGHT_TO_LEFT_OVERRIDE: returntrue; default:
;
}
returnfalse;
}
}
/** Get left, right or centered alignment from RTL context.
Does not return standard, block or repeat, for these the contextual left or right alignment is returned.
*/ static SvxCellHorJustify getAlignmentFromContext( SvxCellHorJustify eInHorJust, bool bCellIsValue, const OUString& rText, const ScPatternAttr& rPattern, const SfxItemSet* pCondSet, const ScDocument* pDoc, SCTAB nTab, constbool bNumberFormatIsText )
{
SvxCellHorJustify eHorJustContext = eInHorJust; bool bUseWritingDirection = false; if (eInHorJust == SvxCellHorJustify::Standard)
{ // fdo#32530: Default alignment depends on value vs // string, and the direction of the 1st letter. if (beginsWithRTLCharacter( rText)) //If language is RTL
{ if (bCellIsValue)
eHorJustContext = bNumberFormatIsText ? SvxCellHorJustify::Right : SvxCellHorJustify::Left; else
eHorJustContext = SvxCellHorJustify::Right;
} elseif (bCellIsValue) //If language is not RTL
eHorJustContext = bNumberFormatIsText ? SvxCellHorJustify::Left : SvxCellHorJustify::Right; else
bUseWritingDirection = true;
}
if (bUseWritingDirection ||
eInHorJust == SvxCellHorJustify::Block || eInHorJust == SvxCellHorJustify::Repeat)
{
SvxFrameDirection nDirection = lcl_GetValue<SvxFrameDirectionItem, SvxFrameDirection>(rPattern, ATTR_WRITINGDIR, pCondSet); if (nDirection == SvxFrameDirection::Horizontal_LR_TB || nDirection == SvxFrameDirection::Vertical_LR_TB)
eHorJustContext = SvxCellHorJustify::Left; elseif (nDirection == SvxFrameDirection::Environment)
{
SAL_WARN_IF( !pDoc, "sc.ui", "getAlignmentFromContext - pDoc==NULL"); // fdo#73588: The content of the cell must also // begin with a RTL character to be right // aligned; otherwise, it should be left aligned.
eHorJustContext = (pDoc && pDoc->IsLayoutRTL(nTab) && (beginsWithRTLCharacter( rText))) ? SvxCellHorJustify::Right : SvxCellHorJustify::Left;
} else
eHorJustContext = SvxCellHorJustify::Right;
} return eHorJustContext;
}
// Try to limit interpreting to only visible cells. Calling e.g. IsValue() // on a formula cell that needs interpreting would call Interpret() // for the entire formula group, which could be large.
mpDoc->InterpretCellsIfNeeded( ScRange( nX1, nY1, nTab, nX2, nY2, nTab ));
// alternative pattern instances in case we need to modify the pattern // before processing the cell value.
std::vector<std::unique_ptr<ScPatternAttr> > aAltPatterns;
// skip text in cell if data bar/icon set is set and only value selected if ( bDoCell )
{ if(pInfo->pDataBar && !pInfo->pDataBar->mbShowValue)
bDoCell = false; if(pInfo->pIconSet && !pInfo->pIconSet->mbShowValue)
bDoCell = false;
}
// output the cell text
ScRefCellValue aCell; if (bDoCell)
{ if ( nCellY == nY && nCellX == nX && nCellX >= nX1 && nCellX <= nX2 )
aCell = pThisRowInfo->cellInfo(nCellX).maCell; else
GetVisibleCell( nCellX, nCellY, nTab, aCell ); // get from document if (aCell.isEmpty())
bDoCell = false; elseif (aCell.getType() == CELLTYPE_EDIT)
bUseEditEngine = true;
}
// Check if this cell is mis-spelled. if (bDoCell && !bUseEditEngine && aCell.getType() == CELLTYPE_STRING)
{ if (mpSpellCheckCxt && mpSpellCheckCxt->isMisspelled(nCellX, nCellY))
bUseEditEngine = true;
}
if (bDoCell && !bUseEditEngine)
{ if ( nCellY == nY && nCellX >= nX1 && nCellX <= nX2 )
{
ScCellInfo& rCellInfo = pThisRowInfo->cellInfo(nCellX);
pPattern = rCellInfo.pPatternAttr;
pCondSet = rCellInfo.pConditionSet;
if ( !pPattern )
{ // #i68085# pattern from cell info for hidden columns is null, // test for null is quicker than using column flags
pPattern = mpDoc->GetPattern( nCellX, nCellY, nTab );
pCondSet = mpDoc->GetCondResult( nCellX, nCellY, nTab );
}
} else// get from document
{
pPattern = mpDoc->GetPattern( nCellX, nCellY, nTab );
pCondSet = mpDoc->GetCondResult( nCellX, nCellY, nTab );
} if ( mpDoc->GetPreviewFont() || mpDoc->GetPreviewCellStyle() )
{
aAltPatterns.push_back(std::make_unique<ScPatternAttr>(*pPattern));
ScPatternAttr* pAltPattern = aAltPatterns.back().get(); if ( ScStyleSheet* pPreviewStyle = mpDoc->GetPreviewCellStyle( nCellX, nCellY, nTab ) )
{
pAltPattern->SetStyleSheet(pPreviewStyle);
} elseif ( SfxItemSet* pFontSet = mpDoc->GetPreviewFont( nCellX, nCellY, nTab ) )
{ if ( const SvxFontItem* pItem = pFontSet->GetItemIfSet( ATTR_FONT ) )
pAltPattern->GetItemSet().Put( *pItem ); if ( const SvxFontItem* pItem = pFontSet->GetItemIfSet( ATTR_CJK_FONT ) )
pAltPattern->GetItemSet().Put( *pItem ); if ( const SvxFontItem* pItem = pFontSet->GetItemIfSet( ATTR_CTL_FONT ) )
pAltPattern->GetItemSet().Put( *pItem );
}
pPattern = pAltPattern;
}
if (aCell.hasNumeric() &&
pPattern->GetItem(ATTR_LINEBREAK, pCondSet).GetValue())
{ // Disable line break when the cell content is numeric.
aAltPatterns.push_back(std::make_unique<ScPatternAttr>(*pPattern));
ScPatternAttr* pAltPattern = aAltPatterns.back().get();
ScLineBreakCell aLineBreak(false);
pAltPattern->GetItemSet().Put(aLineBreak);
pPattern = pAltPattern;
}
sal_uInt16 nShrinkAgain = 0; while ( nNewSize > nAvailable && nShrinkAgain < SC_SHRINKAGAIN_MAX )
{ // If the text is still too large, reduce the scale again by 10%, until it fits, // at most 7 times (it's less than 50% of the calculated scale then).
nScale = ( nScale * 9 ) / 10;
aVars.SetShrinkScale( nScale, nOldScript );
nNewSize = aVars.GetTextSize().Width();
++nShrinkAgain;
} // If even at half the size the font still isn't rendered smaller, // fall back to normal clipping (showing ### for numbers). if ( nNewSize <= nAvailable )
{ // Reset relevant parameters.
aAreaParam.mbLeftClip = aAreaParam.mbRightClip = false;
aAreaParam.mnLeftClipLength = aAreaParam.mnRightClipLength = 0;
}
pOldPattern = nullptr;
}
}
}
if ( bRepeat && !aAreaParam.mbLeftClip && !aAreaParam.mbRightClip )
{
tools::Long nAvailable = aAreaParam.maAlignRect.GetWidth() - nTotalMargin;
tools::Long nRepeatSize = aVars.GetTextSize().Width(); // without margin // When formatting for the printer, the text sizes don't always add up. // Round down (too few repetitions) rather than exceeding the cell size then: if ( pFmtDevice != mpRefDevice )
++nRepeatSize; if ( nRepeatSize > 0 )
{
tools::Long nRepeatCount = nAvailable / nRepeatSize; if ( nRepeatCount > 1 )
{
OUString aCellStr = aVars.GetString();
OUStringBuffer aRepeated(aCellStr); for ( tools::Long nRepeat = 1; nRepeat < nRepeatCount; nRepeat++ )
aRepeated.append(aCellStr);
aVars.SetAutoText( aRepeated.makeStringAndClear() );
}
}
}
// use edit engine if automatic line breaks are needed if ( bBreak )
{ if ( aVars.GetOrient() == SvxCellOrientation::Standard )
bUseEditEngine = ( aAreaParam.mbLeftClip || aAreaParam.mbRightClip ); else
{
tools::Long nHeight = aVars.GetTextSize().Height() + static_cast<tools::Long>(aVars.GetMargin()->GetTopMargin()*mnPPTY) + static_cast<tools::Long>(aVars.GetMargin()->GetBottomMargin()*mnPPTY);
bUseEditEngine = ( nHeight > aAreaParam.maClipRect.GetHeight() );
}
} if (!bUseEditEngine)
{
bUseEditEngine =
aVars.GetHorJust() == SvxCellHorJustify::Block &&
aVars.GetHorJustMethod() == SvxCellJustifyMethod::Distribute;
}
} if (bUseEditEngine)
{ // mark the cell in ScCellInfo to be drawn in DrawEdit: // Cells to the left are marked directly, cells to the // right are handled by the flag for nX2
SCCOL nMarkX = ( nCellX <= nX2 ) ? nCellX : nX2;
pThisRowInfo->basicCellInfo(nMarkX).bEditEngine = true;
bDoCell = false; // don't draw here
// Mark the tagged "TD" structure element to be drawn in DrawEdit if (bTaggedPDF)
{ if (bReopenRowTag)
ReopenPDFStructureElement(vcl::pdf::StructElement::TableRow, nY); else
{
sal_Int32 nId = pPDF->EnsureStructureElement(nullptr);
pPDF->InitStructureElement(nId, vcl::pdf::StructElement::TableRow,
u"TR"_ustr);
pPDF->BeginStructureElement(nId);
pPDF->GetScPDFState()->m_TableRowMap.emplace(nY, nId);
bReopenRowTag = true;
}
if ( nTestClipHeight > nOutHeight )
{ // no vertical clipping when printing cells with optimal height, // except when font size is from conditional formatting. if ( eType != OUTTYPE_PRINTER ||
( mpDoc->GetRowFlags( nCellY, nTab ) & CRFlags::ManualSize ) ||
( aVars.HasCondHeight() ) )
bVClip = true;
}
if ( bHClip || bVClip )
{ // only clip the affected dimension so that not all right-aligned // columns are cut off when performing a non-proportional resize if (!bHClip)
{
aAreaParam.maClipRect.SetLeft( nScrX );
aAreaParam.maClipRect.SetRight( nScrX+nScrW );
} if (!bVClip)
{
aAreaParam.maClipRect.SetTop( nScrY );
aAreaParam.maClipRect.SetBottom( nScrY+nScrH );
}
// aClipRect is not used after SetClipRegion/IntersectClipRegion, // so it can be modified here if (bPixelToLogic)
aAreaParam.maClipRect = mpRefDevice->PixelToLogic( aAreaParam.maClipRect );
Point aURLStart( nJustPosX, nJustPosY ); // copy before modifying for orientation
switch (aVars.GetOrient())
{ case SvxCellOrientation::Standard:
nJustPosY += aVars.GetAscent(); break; case SvxCellOrientation::TopBottom:
nJustPosX += aVars.GetTextSize().Width() - aVars.GetAscent(); break; case SvxCellOrientation::BottomUp:
nJustPosY += aVars.GetTextSize().Height();
nJustPosX += aVars.GetAscent(); break; default:
{ // added to avoid warnings
}
}
// When clipping, the visible part is now completely defined by the alignment, // there's no more special handling to show the right part of RTL text.
Point aDrawTextPos( nJustPosX, nJustPosY ); if ( bPixelToLogic )
{ // undo text width adjustment in pixels if (bRightAdjusted)
aDrawTextPos.AdjustX(aVars.GetTextSize().Width() );
// If the string is clipped, make it shorter for // better performance since drawing by HarfBuzz is // quite expensive especially for long string.
OUString aShort = aString;
// But never fiddle with numeric values. // (Which was the cause of tdf#86024). // The General automatic format output takes // care of this, or fixed width numbers either fit // or display as ###. if (!bCellIsValue)
{ double fVisibleRatio = 1.0; double fTextWidth = aVars.GetTextSize().Width();
sal_Int32 nTextLen = aString.getLength(); if (eOutHorJust == SvxCellHorJustify::Left && aAreaParam.mnRightClipLength > 0)
{
fVisibleRatio = (fTextWidth - aAreaParam.mnRightClipLength) / fTextWidth; if (0.0 < fVisibleRatio && fVisibleRatio < 1.0)
{ // Only show the left-end segment.
sal_Int32 nShortLen = fVisibleRatio*nTextLen + 1;
aShort = aShort.copy(0, nShortLen);
}
} elseif (eOutHorJust == SvxCellHorJustify::Right && aAreaParam.mnLeftClipLength > 0)
{
fVisibleRatio = (fTextWidth - aAreaParam.mnLeftClipLength) / fTextWidth; if (0.0 < fVisibleRatio && fVisibleRatio < 1.0)
{ // Only show the right-end segment.
sal_Int32 nShortLen = fVisibleRatio*nTextLen + 1;
aShort = aShort.copy(nTextLen-nShortLen);
// Adjust the text position after shortening of the string. double fShortWidth = aVars.GetFmtTextWidth(aShort); double fOffset = fTextWidth - fShortWidth;
aDrawTextPos.Move(fOffset, 0);
}
}
}
if ( bProgress )
ScProgress::DeleteInterpretProgress();
}
void ScOutputData::SetRefDevice( OutputDevice* pRDev )
{
mpRefDevice = pFmtDevice = pRDev; // reset EditEngine because it depends on pFmtDevice and mpRefDevice
mxOutputEditEngine.reset();
}
void ScOutputData::SetFmtDevice( OutputDevice* pRDev )
{
pFmtDevice = pRDev; // reset EditEngine because it depends on pFmtDevice
mxOutputEditEngine.reset();
}
void ScOutputData::SetUseStyleColor( bool bSet )
{
mbUseStyleColor = bSet; // reset EditEngine because it depends on mbUseStyleColor
mxOutputEditEngine.reset();
}
void ScOutputData::InitOutputEditEngine()
{ if (!mxOutputEditEngine)
{
mxOutputEditEngine = std::make_unique<ScFieldEditEngine>(mpDoc, mpDoc->GetEnginePool());
mxOutputEditEngine->SetUpdateLayout( false );
mxOutputEditEngine->EnableUndo( false ); // don't need undo for painting purposes // a RefDevice always has to be set, otherwise EditEngine would create a VirtualDevice
mxOutputEditEngine->SetRefDevice( pFmtDevice );
EEControlBits nCtrl = mxOutputEditEngine->GetControlWord(); if ( bShowSpellErrors )
nCtrl |= EEControlBits::ONLINESPELLING; if ( eType == OUTTYPE_PRINTER )
nCtrl &= ~EEControlBits::MARKFIELDS; else
nCtrl &= ~EEControlBits::MARKURLFIELDS; // URLs not shaded for output
mxOutputEditEngine->SetControlWord( nCtrl );
mxOutputEditEngine->EnableAutoColor( mbUseStyleColor );
} else
{ // just in case someone turned it on during the last paint cycle
mxOutputEditEngine->SetUpdateLayout( false );
} // we don't track changes to these settings, so we have to apply them every time
mpDoc->ApplyAsianEditSettings( *mxOutputEditEngine );
mxOutputEditEngine->SetDefaultHorizontalTextDirection( mpDoc->GetEditTextDirection( nTab ) );
}
staticvoid lcl_ClearEdit( EditEngine& rEngine ) // text and attributes
{
rEngine.SetUpdateLayout( false );
rEngine.SetText(OUString()); // do not keep any para-attributes const SfxItemSet& rPara = rEngine.GetParaAttribs(0); if (rPara.Count())
rEngine.SetParaAttribs( 0,
SfxItemSet( *rPara.GetPool(), rPara.GetRanges() ) );
rEngine.EnableSkipOutsideFormat(false);
}
staticbool lcl_SafeIsValue( const ScRefCellValue& rCell )
{ switch (rCell.getType())
{ case CELLTYPE_VALUE: returntrue; case CELLTYPE_FORMULA:
{
ScFormulaCell* pFCell = rCell.getFormula(); if (pFCell->IsRunning() || pFCell->IsValue()) returntrue;
} break; default:
{ // added to avoid warnings
}
} returnfalse;
}
// Don't scale if it fits already. // Allowing to extend into the margin, to avoid scaling at optimal height. if ( nScaleSize <= rAlignRect.GetHeight() ) return;
if ( mbBreak && !mbAsianVertical && pData->HasField() )
{ // Fields aren't wrapped, so clipping is enabled to prevent // a field from being drawn beyond the cell size
if (maMisspellRanges.mpRanges)
mpEngine->SetAllMisspellRanges(*maMisspellRanges.mpRanges);
returntrue;
}
static Color GetConfBackgroundColor()
{ if (const ScTabViewShell* pTabViewShellBg = ScTabViewShell::GetActiveViewShell()) return pTabViewShellBg->GetViewRenderingData().GetDocColor(); return ScModule::get()->GetColorConfig().GetColorValue(svtools::DOCCOLOR).nColor;
}
void ScOutputData::DrawEditParam::setPatternToEngine(bool bUseStyleColor)
{ // syntax highlighting mode is ignored here // StringDiffer doesn't look at hyphenate, language items
if ( !mbHyphenatorSet && bParaHyphenate )
{ // set hyphenator the first time it is needed
css::uno::Reference<css::linguistic2::XHyphenator> xXHyphenator( LinguMgr::GetHyphenator() );
mpEngine->SetHyphenator( xXHyphenator );
mbHyphenatorSet = true;
}
if (mbAsianVertical)
{
rPaperSize.setHeight( rAlignRect.GetHeight() - nTopM - nBottomM ); // Subtract some extra value from the height or else the text would go // outside the cell area. The value of 5 is arbitrary, and is based // entirely on heuristics.
rPaperSize.AdjustHeight( -5 );
}
}
void ScOutputData::DrawEditParam::calcStartPosForVertical(
Point& rLogicStart, tools::Long nCellWidth, tools::Long nEngineWidth, tools::Long nTopM, const OutputDevice* pRefDevice)
{
OSL_ENSURE(isVerticallyOriented(), "Use this only for vertically oriented cell!");
if (mbPixelToLogic)
rLogicStart = pRefDevice->PixelToLogic(rLogicStart);
if (!mbBreak) return;
// vertical adjustment is within the EditEngine if (mbPixelToLogic)
rLogicStart.AdjustY(pRefDevice->PixelToLogic(Size(0,nTopM)).Height() ); else
rLogicStart.AdjustY(nTopM );
switch (meHorJustResult)
{ case SvxCellHorJustify::Center:
rLogicStart.AdjustX((nCellWidth - nEngineWidth) / 2 ); break; case SvxCellHorJustify::Right:
rLogicStart.AdjustX(nCellWidth - nEngineWidth ); break; default:
; // do nothing
}
}
void ScOutputData::DrawEditParam::setAlignmentToEngine()
{ if (isVerticallyOriented() || mbAsianVertical)
{
SvxAdjust eSvxAdjust = SvxAdjust::Left; switch (meVerJust)
{ case SvxCellVerJustify::Top:
eSvxAdjust = (meOrient == SvxCellOrientation::TopBottom || mbAsianVertical) ?
SvxAdjust::Left : SvxAdjust::Right; break; case SvxCellVerJustify::Center:
eSvxAdjust = SvxAdjust::Center; break; case SvxCellVerJustify::Bottom: case SvxCellVerJustify::Standard:
eSvxAdjust = (meOrient == SvxCellOrientation::TopBottom || mbAsianVertical) ?
SvxAdjust::Right : SvxAdjust::Left; break; case SvxCellVerJustify::Block:
eSvxAdjust = SvxAdjust::Block; break;
}
if (meHorJustResult == SvxCellHorJustify::Block)
mpEngine->SetDefaultItem( SvxVerJustifyItem(SvxCellVerJustify::Block, EE_PARA_VER_JUST) );
} else
{ // horizontal alignment now may depend on cell content // (for values with number formats with mixed script types) // -> always set adjustment
SvxAdjust eSvxAdjust = SvxAdjust::Left; if (meOrient == SvxCellOrientation::Stacked)
eSvxAdjust = SvxAdjust::Center; elseif (mbBreak)
{ if (meOrient == SvxCellOrientation::Standard) switch (meHorJustResult)
{ case SvxCellHorJustify::Repeat: // repeat is not yet implemented case SvxCellHorJustify::Standard:
SAL_WARN("sc.ui","meHorJustResult does not match getAlignmentFromContext()");
[[fallthrough]]; case SvxCellHorJustify::Left:
eSvxAdjust = SvxAdjust::Left; break; case SvxCellHorJustify::Center:
eSvxAdjust = SvxAdjust::Center; break; case SvxCellHorJustify::Right:
eSvxAdjust = SvxAdjust::Right; break; case SvxCellHorJustify::Block:
eSvxAdjust = SvxAdjust::Block; break;
} else switch (meVerJust)
{ case SvxCellVerJustify::Top:
eSvxAdjust = SvxAdjust::Right; break; case SvxCellVerJustify::Center:
eSvxAdjust = SvxAdjust::Center; break; case SvxCellVerJustify::Bottom: case SvxCellVerJustify::Standard:
eSvxAdjust = SvxAdjust::Left; break; case SvxCellVerJustify::Block:
eSvxAdjust = SvxAdjust::Block; break;
}
}
mpEngine->SetVertical(mbAsianVertical); if (maCell.getType() == CELLTYPE_EDIT)
{ // We need to synchronize the vertical mode in the EditTextObject // instance too. No idea why we keep this state in two separate // instances. const EditTextObject* pData = maCell.getEditText(); if (pData) const_cast<EditTextObject*>(pData)->SetVertical(mbAsianVertical);
}
}
// Doesn't handle clip marks - should be handled in advance using GetOutputArea class ClearableClipRegion
{ public:
ClearableClipRegion( const tools::Rectangle& rRect, bool bClip, bool bSimClip, const VclPtr<OutputDevice>& pDev, bool bMetaFile )
:mbMetaFile(bMetaFile)
{ if (!(bClip || bSimClip)) return;
maRect = rRect; if (bClip) // for bSimClip only initialize aClipRect
{
mpDev.reset(pDev); if (mbMetaFile)
{
mpDev->Push();
mpDev->IntersectClipRegion(maRect);
} else
mpDev->SetClipRegion(vcl::Region(maRect));
}
}
~ClearableClipRegion() COVERITY_NOEXCEPT_FALSE
{ // Pop() or SetClipRegion() must only be called in case bClip was true // in the ctor, and only then mpDev is set. if (mpDev)
{ if (mbMetaFile)
mpDev->Pop(); else
mpDev->SetClipRegion();
}
}
if ( rParam.meHorJustAttr == SvxCellHorJustify::Repeat )
{ // ignore orientation/rotation if "repeat" is active
rParam.meOrient = SvxCellOrientation::Standard;
nAttrRotate = 0_deg100;
// #i31843# "repeat" with "line breaks" is treated as default alignment // (but rotation is still disabled). // Default again leads to context dependent alignment instead of // SvxCellHorJustify::Standard. if ( rParam.mbBreak )
rParam.meHorJustResult = rParam.meHorJustContext;
}
if (nAttrRotate)
{ //! set flag to find the cell in DrawRotated again ? //! (or flag already set during DrawBackground, then no query here) return; // rotated is outputted separately
}
//! special ScEditUtil handling if formatting for printer
rParam.calcPaperSize(aPaperSize, aAreaParam.maAlignRect, mnPPTX, mnPPTY);
} if (rParam.mbPixelToLogic)
{
Size aLogicSize = mpRefDevice->PixelToLogic(aPaperSize); if ( rParam.mbBreak && !rParam.mbAsianVertical && mpRefDevice != pFmtDevice )
{ // #i85342# screen display and formatting for printer, // use same GetEditArea call as in ScViewData::SetEditEngine
// default alignment for asian vertical mode is top-right if ( rParam.mbAsianVertical && rParam.meVerJust == SvxCellVerJustify::Standard )
rParam.meVerJust = SvxCellVerJustify::Top;
rParam.setPatternToEngine(mbUseStyleColor);
rParam.setAlignmentToEngine(); // Don't format unnecessary parts if the text will be drawn from top (Standard will // act that way if text doesn't fit, see below).
rParam.mpEngine->EnableSkipOutsideFormat(rParam.meVerJust==SvxCellVerJustify::Top
|| rParam.meVerJust==SvxCellVerJustify::Standard);
if ( mbSyntaxMode )
SetEditSyntaxColor(*rParam.mpEngine, rParam.maCell); elseif ( mbUseStyleColor && mbForceAutoColor )
lcl_SetEditColor( *rParam.mpEngine, COL_AUTO ); //! or have a flag at EditEngine
rParam.mpEngine->SetUpdateLayout( true ); // after SetText, before CalcTextWidth/GetTextHeight
// Get final output area using the calculated width
if (!rParam.mbBreak || bShrink)
{ // for break, the first GetOutputArea call is sufficient
GetOutputArea( nXForPos, nArrYForPos, rParam.mnPosX, rParam.mnPosY, rParam.mnCellX, rParam.mnCellY, nNeededPixel,
*rParam.mpPattern, sal::static_int_cast<sal_uInt16>(eOutHorJust),
rParam.mbCellIsValue || bRepeat || bShrink, false, false, aAreaParam );
if ( bShrink )
{
ShrinkEditEngine( *rParam.mpEngine, aAreaParam.maAlignRect,
nLeftM, nTopM, nRightM, nBottomM, true,
rParam.meOrient, 0_deg100, rParam.mbPixelToLogic,
nEngineWidth, nEngineHeight, nNeededPixel,
aAreaParam.mbLeftClip, aAreaParam.mbRightClip );
} if ( bRepeat && !aAreaParam.mbLeftClip && !aAreaParam.mbRightClip && rParam.mpEngine->GetParagraphCount() == 1 )
{ // First check if twice the space for the formatted text is available // (otherwise just keep it unchanged).
tools::Long nFormatted = nNeededPixel - nLeftM - nRightM; // without margin
tools::Long nAvailable = aAreaParam.maAlignRect.GetWidth() - nLeftM - nRightM; if ( nAvailable >= 2 * nFormatted )
{ // "repeat" is handled with unformatted text (for performance reasons)
OUString aCellStr = rParam.mpEngine->GetText();
if (rParam.mbBreak)
{ // text with automatic breaks is aligned only within the // edit engine's paper size, the output of the whole area // is always left-aligned
// Also take fields in a cell with automatic breaks into account: clip to cell width bool bClip = AdjustAreaParamClipRect(aAreaParam) || aAreaParam.mbLeftClip || aAreaParam.mbRightClip || bWrapFields; bool bSimClip = false;
Size aCellSize; // output area, excluding margins, in logical units if (rParam.mbPixelToLogic)
aCellSize = mpRefDevice->PixelToLogic( Size( nOutWidth, nOutHeight ) ); else
aCellSize = Size( nOutWidth, nOutHeight );
// Don't clip for text height when printing rows with optimal height, // except when font size is from conditional formatting. //! Allow clipping when vertically merged? if ( eType != OUTTYPE_PRINTER ||
( mpDoc->GetRowFlags( rParam.mnCellY, nTab ) & CRFlags::ManualSize ) ||
( rParam.mpCondSet && SfxItemState::SET ==
rParam.mpCondSet->GetItemState(ATTR_FONT_HEIGHT) ) )
bClip = true; else
bSimClip = true;
// Show clip marks if height is at least 5pt too small and // there are several lines of text. // Not for asian vertical text, because that would interfere // with the default right position of the text. // Only with automatic line breaks, to avoid having to find // the cells with the horizontal end of the text again. if ( nEngineHeight - aCellSize.Height() > 100 &&
rParam.mbBreak && bMarkClipped &&
( rParam.mpEngine->GetParagraphCount() > 1 || rParam.mpEngine->GetLineCount(0) > 1 ) )
{
ScCellInfo* pClipMarkCell = nullptr; if ( bMerged )
{ // anywhere in the merged area...
SCCOL nClipX = ( rParam.mnX < nX1 ) ? nX1 : rParam.mnX;
pClipMarkCell = &pRowInfo[(rParam.mnArrY != 0) ? rParam.mnArrY : 1].cellInfo(nClipX);
} else
pClipMarkCell = &rParam.mpThisRowInfo->cellInfo(rParam.mnX);
pClipMarkCell->nClipMark |= ScClipMark::Right; //! also allow left?
bAnyClipped = true;
// Standard is normally treated as Bottom, but if text height is clipped, then // Top looks better and also allows using EditEngine::EnableSkipOutsideFormat(). if (rParam.meVerJust==SvxCellVerJustify::Standard)
rParam.meVerJust=SvxCellVerJustify::Top;
}
}
Point aURLStart;
{ // Clip marks are already handled in GetOutputArea
ClearableClipRegion aClip(rParam.mbPixelToLogic ? mpRefDevice->PixelToLogic(aAreaParam.maClipRect)
: aAreaParam.maClipRect, bClip, bSimClip, mpDev, bMetaFile);
Point aLogicStart; if (rParam.mbPixelToLogic)
aLogicStart = mpRefDevice->PixelToLogic( Point(nStartX,nStartY) ); else
aLogicStart = Point(nStartX, nStartY);
if (!rParam.mbBreak)
{ // horizontal alignment if (rParam.adjustHorAlignment(rParam.mpEngine)) // reset adjustment for the next cell
rParam.mpOldPattern = nullptr;
}
if (rParam.meVerJust==SvxCellVerJustify::Bottom ||
rParam.meVerJust==SvxCellVerJustify::Standard)
{ //! if pRefDevice != pFmtDevice, keep heights in logic units, //! only converting margin?
aURLStart = aLogicStart; // copy before modifying for orientation
// bMoveClipped handling has been replaced by complete alignment // handling (also extending to the left).
if (bSimClip)
{ // no hard clip, only draw the affected rows
Point aDocStart = aClip.getRect().TopLeft();
aDocStart -= aLogicStart;
rParam.mpEngine->Draw(*mpDev, aClip.getRect(), aDocStart, false);
} else
{
rParam.mpEngine->Draw(*mpDev, aLogicStart);
}
}
void ScOutputData::ShowClipMarks( DrawEditParam& rParam, tools::Long nEngineWidth, const Size& aCellSize, bool bMerged, OutputAreaParam& aAreaParam, bool bTop)
{ // Show clip marks if width is at least 5pt too small and // there are several lines of text. // Not for asian vertical text, because that would interfere // with the default right position of the text. // Only with automatic line breaks, to avoid having to find // the cells with the horizontal end of the text again. if (nEngineWidth - aCellSize.Width() <= 100 || !rParam.mbBreak || !bMarkClipped
|| (rParam.mpEngine->GetParagraphCount() <= 1 && rParam.mpEngine->GetLineCount(0) <= 1)) return;
if ( mbSyntaxMode )
SetEditSyntaxColor( *rParam.mpEngine, rParam.maCell ); elseif ( mbUseStyleColor && mbForceAutoColor )
lcl_SetEditColor( *rParam.mpEngine, COL_AUTO ); //! or have a flag at EditEngine
rParam.mpEngine->SetUpdateLayout( true ); // after SetText, before CalcTextWidth/GetTextHeight
// Get final output area using the calculated width
if (!rParam.mbBreak || bShrink)
{ // for break, the first GetOutputArea call is sufficient
GetOutputArea( nXForPos, nArrYForPos, rParam.mnPosX, rParam.mnPosY, rParam.mnCellX, rParam.mnCellY, nNeededPixel,
*rParam.mpPattern, sal::static_int_cast<sal_uInt16>(eOutHorJust),
rParam.mbCellIsValue || bRepeat || bShrink, false, false, aAreaParam );
if ( bShrink )
{
ShrinkEditEngine( *rParam.mpEngine, aAreaParam.maAlignRect,
nLeftM, nTopM, nRightM, nBottomM, false,
(rParam.meOrient), 0_deg100, rParam.mbPixelToLogic,
nEngineWidth, nEngineHeight, nNeededPixel,
aAreaParam.mbLeftClip, aAreaParam.mbRightClip );
} if ( bRepeat && !aAreaParam.mbLeftClip && !aAreaParam.mbRightClip && rParam.mpEngine->GetParagraphCount() == 1 )
{ // First check if twice the space for the formatted text is available // (otherwise just keep it unchanged).
const tools::Long nFormatted = nNeededPixel - nLeftM - nRightM; // without margin const tools::Long nAvailable = aAreaParam.maAlignRect.GetWidth() - nLeftM - nRightM; if ( nAvailable >= 2 * nFormatted )
{ // "repeat" is handled with unformatted text (for performance reasons)
OUString aCellStr = rParam.mpEngine->GetText();
if (rParam.mbBreak)
{ // text with automatic breaks is aligned only within the // edit engine's paper size, the output of the whole area // is always left-aligned
Point aLogicStart(nStartX, nStartY);
rParam.calcStartPosForVertical(aLogicStart, aCellSize.Width(), nEngineWidth, nTopM, mpRefDevice);
aURLStart = aLogicStart; // copy before modifying for orientation
if (rParam.meHorJustResult == SvxCellHorJustify::Block || rParam.mbBreak)
{
Size aPSize = rParam.mpEngine->GetPaperSize();
aPSize.setWidth( aCellSize.Height() );
rParam.mpEngine->SetPaperSize(aPSize);
aLogicStart.AdjustY(
rParam.mbBreak ? aPSize.Width() : nEngineHeight );
} else
{ // Note that the "paper" is rotated 90 degrees to the left, so // paper's width is in vertical direction. Also, the whole text // is on a single line, as text wrap is not in effect.
// Set the paper width to be the width of the text.
Size aPSize = rParam.mpEngine->GetPaperSize();
aPSize.setWidth( rParam.mpEngine->CalcTextWidth() );
rParam.mpEngine->SetPaperSize(aPSize);
// First, align text to bottom.
aLogicStart.AdjustY(aCellSize.Height() );
aLogicStart.AdjustY(nTopOffset );
switch (rParam.meVerJust)
{ case SvxCellVerJustify::Standard: case SvxCellVerJustify::Bottom: // align to bottom (do nothing). break; case SvxCellVerJustify::Center: // center it.
aLogicStart.AdjustY( -(nGap / 2) ); break; case SvxCellVerJustify::Block: case SvxCellVerJustify::Top: // align to top
aLogicStart.AdjustY( -nGap ); break; default:
;
}
}
if ( mbSyntaxMode )
SetEditSyntaxColor( *rParam.mpEngine, rParam.maCell ); elseif ( mbUseStyleColor && mbForceAutoColor )
lcl_SetEditColor( *rParam.mpEngine, COL_AUTO ); //! or have a flag at EditEngine
rParam.mpEngine->SetUpdateLayout( true ); // after SetText, before CalcTextWidth/GetTextHeight
// Get final output area using the calculated width
if (!rParam.mbBreak || bShrink)
{ // for break, the first GetOutputArea call is sufficient
GetOutputArea( nXForPos, nArrYForPos, rParam.mnPosX, rParam.mnPosY, rParam.mnCellX, rParam.mnCellY, nNeededPixel,
*rParam.mpPattern, sal::static_int_cast<sal_uInt16>(eOutHorJust),
rParam.mbCellIsValue || bRepeat || bShrink, false, false, aAreaParam );
if ( bShrink )
{
ShrinkEditEngine( *rParam.mpEngine, aAreaParam.maAlignRect,
nLeftM, nTopM, nRightM, nBottomM, false,
rParam.meOrient, 0_deg100, rParam.mbPixelToLogic,
nEngineWidth, nEngineHeight, nNeededPixel,
aAreaParam.mbLeftClip, aAreaParam.mbRightClip );
} if ( bRepeat && !aAreaParam.mbLeftClip && !aAreaParam.mbRightClip && rParam.mpEngine->GetParagraphCount() == 1 )
{ // First check if twice the space for the formatted text is available // (otherwise just keep it unchanged).
const tools::Long nFormatted = nNeededPixel - nLeftM - nRightM; // without margin const tools::Long nAvailable = aAreaParam.maAlignRect.GetWidth() - nLeftM - nRightM; if ( nAvailable >= 2 * nFormatted )
{ // "repeat" is handled with unformatted text (for performance reasons)
OUString aCellStr = rParam.mpEngine->GetText();
if (rParam.mbBreak)
{ // text with automatic breaks is aligned only within the // edit engine's paper size, the output of the whole area // is always left-aligned
Point aLogicStart(nStartX, nStartY);
rParam.calcStartPosForVertical(aLogicStart, aCellSize.Width(), nEngineWidth, nTopM, mpRefDevice);
aURLStart = aLogicStart; // copy before modifying for orientation
if (rParam.meHorJustResult != SvxCellHorJustify::Block)
{
aLogicStart.AdjustX(nEngineWidth ); if (!rParam.mbBreak)
{ // Set the paper width to text size.
Size aPSize = rParam.mpEngine->GetPaperSize();
aPSize.setWidth( rParam.mpEngine->CalcTextWidth() );
rParam.mpEngine->SetPaperSize(aPSize);
switch (rParam.meVerJust)
{ case SvxCellVerJustify::Standard: case SvxCellVerJustify::Bottom: // align to bottom
aLogicStart.AdjustY( -nGap ); break; case SvxCellVerJustify::Center: // center it.
aLogicStart.AdjustY( -(nGap / 2) ); break; case SvxCellVerJustify::Block: case SvxCellVerJustify::Top: // align to top (do nothing) default:
;
}
}
}
// bMoveClipped handling has been replaced by complete alignment // handling (also extending to the left).
if ( rParam.mbAsianVertical )
{ // in asian mode, use EditEngine::SetVertical instead of EEControlBits::ONECHARPERLINE
rParam.meOrient = SvxCellOrientation::Standard;
DrawEditAsianVertical(rParam); return;
}
//! special ScEditUtil handling if formatting for printer
rParam.calcPaperSize(aPaperSize, aAreaParam.maAlignRect, mnPPTX, mnPPTY);
if (rParam.mbPixelToLogic)
{
Size aLogicSize = mpRefDevice->PixelToLogic(aPaperSize); if ( rParam.mbBreak && mpRefDevice != pFmtDevice )
{ // #i85342# screen display and formatting for printer, // use same GetEditArea call as in ScViewData::SetEditEngine
if ( mbSyntaxMode )
SetEditSyntaxColor( *rParam.mpEngine, rParam.maCell ); elseif ( mbUseStyleColor && mbForceAutoColor )
lcl_SetEditColor( *rParam.mpEngine, COL_AUTO ); //! or have a flag at EditEngine
rParam.mpEngine->SetUpdateLayout( true ); // after SetText, before CalcTextWidth/GetTextHeight
// Get final output area using the calculated width
if (rParam.mbBreak)
{ // text with automatic breaks is aligned only within the // edit engine's paper size, the output of the whole area // is always left-aligned
// Also take fields in a cell with automatic breaks into account: clip to cell width bool bClip = AdjustAreaParamClipRect(aAreaParam) || aAreaParam.mbLeftClip || aAreaParam.mbRightClip || bWrapFields; bool bSimClip = false;
Size aCellSize; // output area, excluding margins, in logical units if (rParam.mbPixelToLogic)
aCellSize = mpRefDevice->PixelToLogic( Size( nOutWidth, nOutHeight ) ); else
aCellSize = Size( nOutWidth, nOutHeight );
// Don't clip for text height when printing rows with optimal height, // except when font size is from conditional formatting. //! Allow clipping when vertically merged? if ( eType != OUTTYPE_PRINTER ||
( mpDoc->GetRowFlags( rParam.mnCellY, nTab ) & CRFlags::ManualSize ) ||
( rParam.mpCondSet && SfxItemState::SET ==
rParam.mpCondSet->GetItemState(ATTR_FONT_HEIGHT) ) )
bClip = true; else
bSimClip = true;
// Show clip marks if height is at least 5pt too small and // there are several lines of text. // Not for asian vertical text, because that would interfere // with the default right position of the text. // Only with automatic line breaks, to avoid having to find // the cells with the horizontal end of the text again. if ( nEngineHeight - aCellSize.Height() > 100 &&
rParam.mbBreak && bMarkClipped &&
( rParam.mpEngine->GetParagraphCount() > 1 || rParam.mpEngine->GetLineCount(0) > 1 ) )
{
ScCellInfo* pClipMarkCell = nullptr; if ( bMerged )
{ // anywhere in the merged area...
SCCOL nClipX = ( rParam.mnX < nX1 ) ? nX1 : rParam.mnX;
pClipMarkCell = &pRowInfo[(rParam.mnArrY != 0) ? rParam.mnArrY : 1].cellInfo(nClipX);
} else
pClipMarkCell = &rParam.mpThisRowInfo->cellInfo(rParam.mnX);
pClipMarkCell->nClipMark |= ScClipMark::Right; //! also allow left?
bAnyClipped = true;
{ // Clip marks are already handled in GetOutputArea
ClearableClipRegion aClip(rParam.mbPixelToLogic ? mpRefDevice->PixelToLogic(aAreaParam.maClipRect)
: aAreaParam.maClipRect, bClip, bSimClip, mpDev, bMetaFile);
Point aLogicStart; if (rParam.mbPixelToLogic)
aLogicStart = mpRefDevice->PixelToLogic( Point(nStartX,nStartY) ); else
aLogicStart = Point(nStartX, nStartY);
if (rParam.meVerJust==SvxCellVerJustify::Bottom ||
rParam.meVerJust==SvxCellVerJustify::Standard)
{ //! if pRefDevice != pFmtDevice, keep heights in logic units, //! only converting margin?
// bMoveClipped handling has been replaced by complete alignment // handling (also extending to the left).
if (bSimClip)
{ // no hard clip, only draw the affected rows
Point aDocStart = aClip.getRect().TopLeft();
aDocStart -= aLogicStart;
rParam.mpEngine->Draw(*mpDev, aClip.getRect(), aDocStart, false);
} else
{
rParam.mpEngine->Draw(*mpDev, aLogicStart);
}
}
void ScOutputData::DrawEditAsianVertical(DrawEditParam& rParam)
{ // When in asian vertical orientation, the orientation value is STANDARD, // and the asian vertical boolean is true.
OSL_ASSERT(rParam.meOrient == SvxCellOrientation::Standard);
OSL_ASSERT(rParam.mbAsianVertical);
OSL_ASSERT(rParam.meHorJustAttr != SvxCellHorJustify::Repeat);
if (nAttrRotate)
{ //! set flag to find the cell in DrawRotated again ? //! (or flag already set during DrawBackground, then no query here)
bHidden = true; // rotated is outputted separately
}
// default alignment for asian vertical mode is top-right /* TODO: is setting meHorJustContext and meHorJustResult unconditionally to * SvxCellHorJustify::Right really wanted? Seems this was done all the time,
* also before context was introduced and everything was attr only. */ if ( rParam.meHorJustAttr == SvxCellHorJustify::Standard )
rParam.meHorJustResult = rParam.meHorJustContext = SvxCellHorJustify::Right;
//! special ScEditUtil handling if formatting for printer
rParam.calcPaperSize(aPaperSize, aAreaParam.maAlignRect, mnPPTX, mnPPTY);
if (rParam.mbPixelToLogic)
{
Size aLogicSize = mpRefDevice->PixelToLogic(aPaperSize); if ( rParam.mbBreak && !rParam.mbAsianVertical && mpRefDevice != pFmtDevice )
{ // #i85342# screen display and formatting for printer, // use same GetEditArea call as in ScViewData::SetEditEngine
// default alignment for asian vertical mode is top-right if ( rParam.meVerJust == SvxCellVerJustify::Standard )
rParam.meVerJust = SvxCellVerJustify::Top;
if ( mbSyntaxMode )
SetEditSyntaxColor( *rParam.mpEngine, rParam.maCell ); elseif ( mbUseStyleColor && mbForceAutoColor )
lcl_SetEditColor( *rParam.mpEngine, COL_AUTO ); //! or have a flag at EditEngine
rParam.mpEngine->SetUpdateLayout( true ); // after SetText, before CalcTextWidth/GetTextHeight
// Get final output area using the calculated width
// Also take fields in a cell with automatic breaks into account: clip to cell width bool bClip = AdjustAreaParamClipRect(aAreaParam) || aAreaParam.mbLeftClip || aAreaParam.mbRightClip || bWrapFields; bool bSimClip = false;
Size aCellSize; // output area, excluding margins, in logical units if (rParam.mbPixelToLogic)
aCellSize = mpRefDevice->PixelToLogic( Size( nOutWidth, nOutHeight ) ); else
aCellSize = Size( nOutWidth, nOutHeight );
// Don't clip for text height when printing rows with optimal height, // except when font size is from conditional formatting. //! Allow clipping when vertically merged? if ( eType != OUTTYPE_PRINTER ||
( mpDoc->GetRowFlags( rParam.mnCellY, nTab ) & CRFlags::ManualSize ) ||
( rParam.mpCondSet && SfxItemState::SET ==
rParam.mpCondSet->GetItemState(ATTR_FONT_HEIGHT) ) )
bClip = true; else
bSimClip = true;
// Show clip marks if height is at least 5pt too small and // there are several lines of text. // Not for asian vertical text, because that would interfere // with the default right position of the text. // Only with automatic line breaks, to avoid having to find // the cells with the horizontal end of the text again. if ( nEngineHeight - aCellSize.Height() > 100 &&
( rParam.mbBreak || rParam.meOrient == SvxCellOrientation::Stacked ) &&
!rParam.mbAsianVertical && bMarkClipped &&
( rParam.mpEngine->GetParagraphCount() > 1 || rParam.mpEngine->GetLineCount(0) > 1 ) )
{
ScCellInfo* pClipMarkCell = nullptr; if ( bMerged )
{ // anywhere in the merged area...
SCCOL nClipX = ( rParam.mnX < nX1 ) ? nX1 : rParam.mnX;
pClipMarkCell = &pRowInfo[(rParam.mnArrY != 0) ? rParam.mnArrY : 1].cellInfo(nClipX);
} else
pClipMarkCell = &rParam.mpThisRowInfo->cellInfo(rParam.mnX);
pClipMarkCell->nClipMark |= ScClipMark::Right; //! also allow left?
bAnyClipped = true;
// paper size is subtracted below
aLogicStart.AdjustX(nEngineWidth );
// vertical adjustment is within the EditEngine if (rParam.mbPixelToLogic)
aLogicStart.AdjustY(mpRefDevice->PixelToLogic(Size(0,nTopM)).Height() ); else
aLogicStart.AdjustY(nTopM );
aURLStart = aLogicStart; // copy before modifying for orientation
// bMoveClipped handling has been replaced by complete alignment // handling (also extending to the left).
// with SetVertical, the start position is top left of // the whole output area, not the text itself
aLogicStart.AdjustX( -(rParam.mpEngine->GetPaperSize().Width()) );
tools::Long nRowPosY = nScrY; for (SCSIZE nArrY=0; nArrY+1<nArrCount; nArrY++) // 0 of the rest of the merged
{
RowInfo* pThisRowInfo = &pRowInfo[nArrY];
if (nArrY==1) nRowPosY = nScrY; // positions before are calculated individually
if ( pThisRowInfo->bChanged || nArrY==0 )
{
tools::Long nPosX = 0; for (SCCOL nX=0; nX<=nX2; nX++) // due to overflow
{
std::unique_ptr< ScPatternAttr > pPreviewPattr; if (nX==nX1) nPosX = nInitPosX; // positions before nX1 are calculated individually
if (pThisRowInfo->basicCellInfo(nX).bEditEngine)
{
SCROW nY = pThisRowInfo->nRowNo;
SCCOL nCellX = nX; // position where the cell really starts
SCROW nCellY = nY; bool bDoCell = false;
// if merged cell contains hidden row or column or both const ScMergeFlagAttr* pMergeFlag = mpDoc->GetAttr(nX, nY, nTab, ATTR_MERGE_FLAG); bool bOverlapped = (pMergeFlag->IsHorOverlapped() || pMergeFlag->IsVerOverlapped());
tools::Long nPosY = nRowPosY; if (bOverlapped)
{
nY = pRowInfo[nArrY].nRowNo;
SCCOL nOverX; // start of the merged cells
SCROW nOverY; if (GetMergeOrigin( nX,nY, nArrY, nOverX,nOverY, true ))
{
nCellX = nOverX;
nCellY = nOverY;
bDoCell = true;
}
} elseif ( nX == nX2 && pThisRowInfo->cellInfo(nX).maCell.isEmpty() )
{ // Rest of a long text further to the right?
SCCOL nTempX=nX; while (nTempX < nLastContentCol && IsEmptyCellText( pThisRowInfo, nTempX, nY ))
++nTempX;
if (aParam.meHorJustAttr == SvxCellHorJustify::Repeat)
{ // ignore orientation/rotation if "repeat" is active
aParam.meOrient = SvxCellOrientation::Standard;
} switch (aParam.meOrient)
{ case SvxCellOrientation::BottomUp:
DrawEditBottomTop(aParam); break; case SvxCellOrientation::TopBottom:
DrawEditTopBottom(aParam); break; case SvxCellOrientation::Stacked: // this can be vertically stacked or asian vertical.
DrawEditStacked(aParam); break; default:
DrawEditStandard(aParam);
}
tools::Long nRowPosY = nScrY; for (SCSIZE nArrY=0; nArrY+1<nArrCount; nArrY++) // 0 for the rest of the merged
{
RowInfo* pThisRowInfo = &pRowInfo[nArrY];
tools::Long nCellHeight = static_cast<tools::Long>(pThisRowInfo->nHeight); if (nArrY==1) nRowPosY = nScrY; // positions before are calculated individually
if ( ( pThisRowInfo->bChanged || nArrY==0 ) && pThisRowInfo->nRotMaxCol != SC_ROTMAX_NONE )
{
tools::Long nPosX = 0; for (SCCOL nX=0; nX<=nRotMax; nX++)
{ if (nX==nX1) nPosX = nInitPosX; // positions before nX1 are calculated individually
const ScCellInfo* pInfo = &pThisRowInfo->cellInfo(nX); if ( pInfo->nRotateDir != ScRotateDir::NONE )
{
SCROW nY = pThisRowInfo->nRowNo;
bool bHidden = false; if (bEditMode) if ( nX == nEditCol && nY == nEditRow )
bHidden = true;
if (!bHidden)
{
lcl_ClearEdit( *mxOutputEditEngine ); // also calls SetUpdateMode(sal_False)
tools::Long nPosY = nRowPosY;
//! rest from merged cells further up do not work!
if ( !bHyphenatorSet && bParaHyphenate )
{ // set hyphenator the first time it is needed
css::uno::Reference<css::linguistic2::XHyphenator> xXHyphenator( LinguMgr::GetHyphenator() );
mxOutputEditEngine->SetHyphenator( xXHyphenator );
bHyphenatorSet = true;
}
// rotate here already, to adjust paper size for page breaks
Degree100 nAttrRotate; double nSin = 0.0; double nCos = 1.0;
SvxRotateMode eRotMode = SVX_ROTATE_MODE_STANDARD; if ( eOrient == SvxCellOrientation::Standard )
{
nAttrRotate = pPattern->
GetItem(ATTR_ROTATE_VALUE, pCondSet).GetValue(); if ( nAttrRotate )
{
eRotMode = pPattern->GetItem(ATTR_ROTATE_MODE, pCondSet).GetValue();
// tdf#143377 To use the same limits to avoid too big Skew // with TextOrientation in Calc, use 1/2 degree here, too. // This equals '50' in the notation here (100th degree) staticconst sal_Int32 nMinRad(50);
// bring nAttrRotate to the range [0..36000[
nAttrRotate = Degree100(((nAttrRotate.get() % 36000) + 36000) % 36000);
// check for to be avoided extreme values and correct if (nAttrRotate < Degree100(nMinRad))
{ // range [0..50]
nAttrRotate = Degree100(nMinRad);
eRotMode = SVX_ROTATE_MODE_STANDARD; // no overflow
} elseif (nAttrRotate > Degree100(36000 - nMinRad))
{ // range [35950..36000[
nAttrRotate = Degree100(36000 - nMinRad);
eRotMode = SVX_ROTATE_MODE_STANDARD; // no overflow
} elseif (nAttrRotate > Degree100(18000 - nMinRad) && (nAttrRotate < Degree100(18000 + nMinRad)))
{ // range 50 around 18000, [17950..18050]
nAttrRotate = (nAttrRotate > Degree100(18000))
? Degree100(18000 + nMinRad)
: Degree100(18000 - nMinRad);
eRotMode = SVX_ROTATE_MODE_STANDARD; // no overflow
}
if ( bLayoutRTL )
{ // keep in range [0..36000[
nAttrRotate = Degree100(36000 - nAttrRotate.get());
}
// tdf#143377 new strategy: instead of using zero for nSin, which // would be the *correct* value, continue with the corrected maximum // allowed value which is then *not* zero. This is similar to // the behaviour before where (just due to numerical unprecisions) // nSin was also not zero (pure coincidence), but very close to it. // I checked and tried to make safe all places below that use // nSin and divide by it, but there is too much going on and that // would not be safe, so rely on the same values as before, but // now numerically limited to not get the Skew go havoc
nSin = sin( nRealOrient );
}
}
Size aPaperSize( 1000000, 1000000 ); if (eOrient==SvxCellOrientation::Stacked)
aPaperSize.setWidth( nOutWidth ); // to center elseif (bBreak)
{ if (nAttrRotate)
{ //! the correct paper size for break depends on the number //! of rows, as long as the rows can not be outputted individually //! offsetted -> therefore unlimited, so no wrapping. //! With offset rows the following would be correct:
aPaperSize.setWidth( static_cast<tools::Long>(nOutHeight / fabs(nSin)) );
} elseif (eOrient == SvxCellOrientation::Standard)
aPaperSize.setWidth( nOutWidth ); else
aPaperSize.setWidth( nOutHeight - 1 );
} if (bPixelToLogic)
mxOutputEditEngine->SetPaperSize(mpRefDevice->PixelToLogic(aPaperSize)); else
mxOutputEditEngine->SetPaperSize(aPaperSize); // scale is always 1
if ( mbSyntaxMode )
{
SetEditSyntaxColor(*mxOutputEditEngine, aCell);
} elseif ( mbUseStyleColor && mbForceAutoColor )
lcl_SetEditColor( *mxOutputEditEngine, COL_AUTO ); //! or have a flag at EditEngine
mxOutputEditEngine->SetUpdateLayout( true ); // after SetText, before CalcTextWidth/GetTextHeight
// adjust width of papersize for height of text int nSteps = 5; while (nSteps > 0)
{ // everything is in pixels
tools::Long nEnginePixel = mpRefDevice->LogicToPixel(
Size(0,nEngineHeight)).Height();
tools::Long nEffHeight = nOutHeight - static_cast<tools::Long>(nEnginePixel * nAbsCos) + 2;
tools::Long nNewWidth = static_cast<tools::Long>(nEffHeight / nAbsSin) + 2; bool bFits = ( nNewWidth >= aPaperSize.Width() ); if ( bFits )
nSteps = 0; else
{ if ( nNewWidth < 4 )
{ // can't fit -> fall back to using half height
nEffHeight = nOutHeight / 2;
nNewWidth = static_cast<tools::Long>(nEffHeight / nAbsSin) + 2;
nSteps = 0;
} else
--nSteps;
// set paper width and get new text height
aPaperSize.setWidth( nNewWidth ); if (bPixelToLogic)
mxOutputEditEngine->SetPaperSize(mpRefDevice->PixelToLogic(aPaperSize)); else
mxOutputEditEngine->SetPaperSize(aPaperSize); // Scale is always 1 //mxOutputEditEngine->QuickFormatDoc( sal_True );
if ( eRotMode == SVX_ROTATE_MODE_STANDARD )
{ // do width only if rotating within the cell (standard mode)
ShrinkEditEngine( *mxOutputEditEngine, aAreaParam.maAlignRect, nLeftM, nTopM, nRightM, nBottomM, true, eOrient, nAttrRotate, bPixelToLogic,
nEngineWidth, nEngineHeight, nNeededPixel, aAreaParam.mbLeftClip, aAreaParam.mbRightClip );
}
// nEngineWidth/nEngineHeight is updated in ShrinkEditEngine // (but width is only valid for standard mode)
nRealWidth = static_cast<tools::Long>(mxOutputEditEngine->CalcTextWidth());
nRealHeight = mxOutputEditEngine->GetTextHeight();
// TOPBOTTOM and BOTTOMTOP are handled in DrawStrings/DrawEdit
OSL_ENSURE( eOrient == SvxCellOrientation::Standard && nAttrRotate, "DrawRotated: no rotation" );
Degree10 nOriVal = 0_deg10; if ( nAttrRotate )
{ // attribute is 1/100, Font 1/10 degrees
nOriVal = to<Degree10>(nAttrRotate);
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.