diff options
Diffstat (limited to 'sc/source/ui/view/output2.cxx')
-rw-r--r-- | sc/source/ui/view/output2.cxx | 5123 |
1 files changed, 5123 insertions, 0 deletions
diff --git a/sc/source/ui/view/output2.cxx b/sc/source/ui/view/output2.cxx new file mode 100644 index 000000000..5c234df79 --- /dev/null +++ b/sc/source/ui/view/output2.cxx @@ -0,0 +1,5123 @@ +/* -*- 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 . + */ + +#include <scitems.hxx> +#include <editeng/eeitem.hxx> + +#include <editeng/adjustitem.hxx> +#include <svx/algitem.hxx> +#include <editeng/brushitem.hxx> +#include <svtools/colorcfg.hxx> +#include <editeng/colritem.hxx> +#include <editeng/charreliefitem.hxx> +#include <editeng/crossedoutitem.hxx> +#include <editeng/contouritem.hxx> +#include <editeng/editobj.hxx> +#include <editeng/editstat.hxx> +#include <editeng/emphasismarkitem.hxx> +#include <editeng/fhgtitem.hxx> +#include <editeng/forbiddenruleitem.hxx> +#include <editeng/frmdiritem.hxx> +#include <editeng/justifyitem.hxx> +#include <svx/rotmodit.hxx> +#include <editeng/udlnitem.hxx> +#include <editeng/unolingu.hxx> +#include <editeng/fontitem.hxx> +#include <editeng/postitem.hxx> +#include <editeng/shdditem.hxx> +#include <editeng/wghtitem.hxx> +#include <editeng/wrlmitem.hxx> +#include <formula/errorcodes.hxx> +#include <svl/numformat.hxx> +#include <svl/zforlist.hxx> +#include <svl/zformat.hxx> +#include <vcl/svapp.hxx> +#include <vcl/metric.hxx> +#include <vcl/outdev.hxx> +#include <vcl/pdfextoutdevdata.hxx> +#include <vcl/settings.hxx> +#include <vcl/glyphitem.hxx> +#include <vcl/vcllayout.hxx> +#include <vcl/glyphitemcache.hxx> +#include <sal/log.hxx> +#include <unotools/charclass.hxx> +#include <osl/diagnose.h> +#include <tools/stream.hxx> + +#include <output.hxx> +#include <document.hxx> +#include <formulacell.hxx> +#include <attrib.hxx> +#include <patattr.hxx> +#include <cellform.hxx> +#include <editutil.hxx> +#include <progress.hxx> +#include <scmod.hxx> +#include <fillinfo.hxx> +#include <stlsheet.hxx> +#include <spellcheckcontext.hxx> +#include <scopetools.hxx> + +#include <com/sun/star/i18n/DirectionProperty.hpp> +#include <comphelper/scopeguard.hxx> +#include <comphelper/string.hxx> + +#include <memory> +#include <vector> +#include <o3tl/lru_map.hxx> +#include <o3tl/hash_combine.hxx> + +#include <math.h> + +using namespace com::sun::star; + +//! Merge Autofilter width with column.cxx +#define DROPDOWN_BITMAP_SIZE 18 + +#define DRAWTEXT_MAX 32767 + +const sal_uInt16 SC_SHRINKAGAIN_MAX = 7; +constexpr auto HMM_PER_TWIPS = o3tl::convert(1.0, o3tl::Length::twip, o3tl::Length::mm100); + +class ScDrawStringsVars +{ + ScOutputData* pOutput; // connection + + const ScPatternAttr* pPattern; // attribute + const SfxItemSet* pCondSet; // from conditional formatting + + vcl::Font aFont; // created from attributes + FontMetric aMetric; + tools::Long nAscentPixel; // always pixels + SvxCellOrientation eAttrOrient; + SvxCellHorJustify eAttrHorJust; + SvxCellVerJustify eAttrVerJust; + SvxCellJustifyMethod eAttrHorJustMethod; + const SvxMarginItem* pMargin; + sal_uInt16 nIndent; + bool bRotated; + + OUString aString; // contents + Size aTextSize; + tools::Long nOriginalWidth; + tools::Long nMaxDigitWidth; + tools::Long nSignWidth; + tools::Long nDotWidth; + tools::Long nExpWidth; + + ScRefCellValue maLastCell; + sal_uLong nValueFormat; + bool bLineBreak; + bool bRepeat; + bool bShrink; + + bool bPixelToLogic; + bool bCellContrast; + + Color aBackConfigColor; // used for ScPatternAttr::GetFont calls + Color aTextConfigColor; + sal_Int32 nRepeatPos; + sal_Unicode nRepeatChar; + +public: + ScDrawStringsVars(ScOutputData* pData, bool bPTL); + + // SetPattern = ex-SetVars + // SetPatternSimple: without Font + + void SetPattern( + const ScPatternAttr* pNew, const SfxItemSet* pSet, const ScRefCellValue& rCell, + SvtScriptType nScript ); + + void SetPatternSimple( const ScPatternAttr* pNew, const SfxItemSet* pSet ); + + bool SetText( const ScRefCellValue& rCell ); // TRUE -> drop pOldPattern + void SetHashText(); + void SetTextToWidthOrHash( ScRefCellValue& rCell, tools::Long nWidth ); + void SetAutoText( const OUString& rAutoText ); + + SvxCellOrientation GetOrient() const { return eAttrOrient; } + SvxCellHorJustify GetHorJust() const { return eAttrHorJust; } + SvxCellVerJustify GetVerJust() const { return eAttrVerJust; } + SvxCellJustifyMethod GetHorJustMethod() const { return eAttrHorJustMethod; } + const SvxMarginItem* GetMargin() const { return pMargin; } + + sal_uInt16 GetLeftTotal() const { return pMargin->GetLeftMargin() + nIndent; } + sal_uInt16 GetRightTotal() const { return pMargin->GetRightMargin() + nIndent; } + + const OUString& GetString() const { return aString; } + const Size& GetTextSize() const { return aTextSize; } + tools::Long GetOriginalWidth() const { return nOriginalWidth; } + tools::Long GetFmtTextWidth(const OUString& rString); + + // 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;} + + bool GetLineBreak() const { return bLineBreak; } + bool IsRepeat() const { return bRepeat; } + bool IsShrink() const { return bShrink; } + void RepeatToFill( tools::Long nColWidth ); + + tools::Long GetAscent() const { return nAscentPixel; } + bool IsRotated() const { return bRotated; } + + void SetShrinkScale( tools::Long nScale, SvtScriptType nScript ); + + bool HasCondHeight() const { return pCondSet && SfxItemState::SET == + pCondSet->GetItemState( ATTR_FONT_HEIGHT ); } + + bool HasEditCharacters() const; + + // 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(); +}; + +ScDrawStringsVars::ScDrawStringsVars(ScOutputData* pData, bool bPTL) : + pOutput ( pData ), + pPattern ( nullptr ), + pCondSet ( nullptr ), + nAscentPixel(0), + eAttrOrient ( SvxCellOrientation::Standard ), + eAttrHorJust( SvxCellHorJustify::Standard ), + eAttrVerJust( SvxCellVerJustify::Bottom ), + eAttrHorJustMethod( SvxCellJustifyMethod::Auto ), + pMargin ( nullptr ), + nIndent ( 0 ), + bRotated ( false ), + nOriginalWidth( 0 ), + nMaxDigitWidth( 0 ), + nSignWidth( 0 ), + nDotWidth( 0 ), + nExpWidth( 0 ), + nValueFormat( 0 ), + bLineBreak ( false ), + bRepeat ( false ), + bShrink ( false ), + bPixelToLogic( bPTL ), + nRepeatPos( -1 ), + nRepeatChar( 0x0 ) +{ + ScModule* pScMod = SC_MOD(); + bCellContrast = pOutput->mbUseStyleColor && + Application::GetSettings().GetStyleSettings().GetHighContrastMode(); + + const svtools::ColorConfig& rColorConfig = pScMod->GetColorConfig(); + aBackConfigColor = rColorConfig.GetColorValue(svtools::DOCCOLOR).nColor; + aTextConfigColor = rColorConfig.GetColorValue(svtools::FONTCOLOR).nColor; +} + +void ScDrawStringsVars::SetShrinkScale( tools::Long nScale, SvtScriptType nScript ) +{ + // text remains valid, size is updated + + OutputDevice* pDev = pOutput->mpDev; + OutputDevice* pRefDevice = pOutput->mpRefDevice; + OutputDevice* pFmtDevice = pOutput->pFmtDevice; + + // call GetFont with a modified fraction, use only the height + + Fraction aFraction( nScale, 100 ); + if ( !bPixelToLogic ) + aFraction *= pOutput->aZoomY; + vcl::Font aTmpFont; + pPattern->GetFont( aTmpFont, SC_AUTOCOL_RAW, pFmtDevice, &aFraction, pCondSet, nScript ); + tools::Long nNewHeight = aTmpFont.GetFontHeight(); + if ( nNewHeight > 0 ) + aFont.SetFontHeight( nNewHeight ); + + // set font and dependent variables as in SetPattern + + pDev->SetFont( aFont ); + if ( pFmtDevice != pDev ) + pFmtDevice->SetFont( aFont ); + + aMetric = pFmtDevice->GetFontMetric(); + 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 ); + } + + nAscentPixel = aMetric.GetAscent(); + if ( bPixelToLogic ) + nAscentPixel = pRefDevice->LogicToPixel( Size( 0, nAscentPixel ) ).Height(); + + SetAutoText( aString ); // same text again, to get text size +} + +namespace { + +template<typename ItemType, typename EnumType> +EnumType lcl_GetValue(const ScPatternAttr& rPattern, sal_uInt16 nWhich, const SfxItemSet* pCondSet) +{ + const ItemType& rItem = static_cast<const ItemType&>(rPattern.GetItem(nWhich, pCondSet)); + return static_cast<EnumType>(rItem.GetValue()); +} + +bool lcl_GetBoolValue(const ScPatternAttr& rPattern, sal_uInt16 nWhich, const SfxItemSet* pCondSet) +{ + return lcl_GetValue<SfxBoolItem, bool>(rPattern, nWhich, pCondSet); +} + +} + +static bool lcl_isNumberFormatText(const ScDocument* pDoc, SCCOL nCellX, SCROW nCellY, SCTAB nTab ) +{ + sal_uInt32 nCurrentNumberFormat = pDoc->GetNumberFormat( nCellX, nCellY, nTab ); + SvNumberFormatter* pNumberFormatter = pDoc->GetFormatTable(); + return pNumberFormatter->GetType( nCurrentNumberFormat ) == SvNumFormatType::TEXT; +} + +void ScDrawStringsVars::SetPattern( + const ScPatternAttr* pNew, const SfxItemSet* pSet, const ScRefCellValue& rCell, + SvtScriptType nScript ) +{ + nMaxDigitWidth = 0; + nSignWidth = 0; + nDotWidth = 0; + nExpWidth = 0; + + pPattern = pNew; + pCondSet = pSet; + + // evaluate pPattern + + OutputDevice* pDev = pOutput->mpDev; + OutputDevice* pRefDevice = pOutput->mpRefDevice; + OutputDevice* pFmtDevice = pOutput->pFmtDevice; + + // font + + ScAutoFontColorMode eColorMode; + if ( pOutput->mbUseStyleColor ) + { + if ( pOutput->mbForceAutoColor ) + eColorMode = bCellContrast ? SC_AUTOCOL_IGNOREALL : SC_AUTOCOL_IGNOREFONT; + else + eColorMode = bCellContrast ? SC_AUTOCOL_IGNOREBACK : SC_AUTOCOL_DISPLAY; + } + else + eColorMode = SC_AUTOCOL_PRINT; + + if ( bPixelToLogic ) + pPattern->GetFont( aFont, eColorMode, pFmtDevice, nullptr, pCondSet, nScript, + &aBackConfigColor, &aTextConfigColor ); + else + pPattern->GetFont( aFont, eColorMode, pFmtDevice, &pOutput->aZoomY, pCondSet, nScript, + &aBackConfigColor, &aTextConfigColor ); + aFont.SetAlignment(ALIGN_BASELINE); + + // orientation + + eAttrOrient = pPattern->GetCellOrientation( pCondSet ); + + // alignment + + eAttrHorJust = pPattern->GetItem( ATTR_HOR_JUSTIFY, pCondSet ).GetValue(); + + eAttrVerJust = pPattern->GetItem( ATTR_VER_JUSTIFY, pCondSet ).GetValue(); + if ( eAttrVerJust == SvxCellVerJustify::Standard ) + eAttrVerJust = SvxCellVerJustify::Bottom; + + // justification method + + eAttrHorJustMethod = lcl_GetValue<SvxJustifyMethodItem, SvxCellJustifyMethod>(*pPattern, ATTR_HOR_JUSTIFY_METHOD, pCondSet); + + // line break + + bLineBreak = pPattern->GetItem( ATTR_LINEBREAK, pCondSet ).GetValue(); + + // handle "repeat" alignment + + bRepeat = ( eAttrHorJust == SvxCellHorJustify::Repeat ); + if ( bRepeat ) + { + // "repeat" disables rotation (before constructing the font) + eAttrOrient = SvxCellOrientation::Standard; + + // #i31843# "repeat" with "line breaks" is treated as default alignment (but rotation is still disabled) + if ( bLineBreak ) + eAttrHorJust = SvxCellHorJustify::Standard; + } + + sal_Int16 nRot; + switch (eAttrOrient) + { + case SvxCellOrientation::Standard: + nRot = 0; + bRotated = pPattern->GetItem( ATTR_ROTATE_VALUE, pCondSet ).GetValue() != 0_deg100 && + !bRepeat; + break; + case SvxCellOrientation::Stacked: + nRot = 0; + bRotated = false; + break; + case SvxCellOrientation::TopBottom: + nRot = 2700; + bRotated = false; + break; + case SvxCellOrientation::BottomUp: + nRot = 900; + bRotated = false; + break; + default: + OSL_FAIL("Invalid SvxCellOrientation value"); + nRot = 0; + bRotated = false; + break; + } + aFont.SetOrientation( Degree10(nRot) ); + + // syntax mode + + 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. + aFont.SetKerning( FontKerning::NONE); + + pDev->SetFont( aFont ); + if ( pFmtDevice != pDev ) + pFmtDevice->SetFont( aFont ); + + aMetric = pFmtDevice->GetFontMetric(); + + // 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 ); + } + + nAscentPixel = aMetric.GetAscent(); + if ( bPixelToLogic ) + nAscentPixel = pRefDevice->LogicToPixel( Size( 0, nAscentPixel ) ).Height(); + + Color aULineColor( pPattern->GetItem( ATTR_FONT_UNDERLINE, pCondSet ).GetColor() ); + pDev->SetTextLineColor( aULineColor ); + + Color aOLineColor( pPattern->GetItem( ATTR_FONT_OVERLINE, pCondSet ).GetColor() ); + pDev->SetOverlineColor( aOLineColor ); + + // number format + + nValueFormat = pPattern->GetNumberFormat( pOutput->mpDoc->GetFormatTable(), pCondSet ); + + // margins + pMargin = &pPattern->GetItem( ATTR_MARGIN, pCondSet ); + if ( eAttrHorJust == SvxCellHorJustify::Left || eAttrHorJust == SvxCellHorJustify::Right ) + nIndent = pPattern->GetItem( ATTR_INDENT, pCondSet ).GetValue(); + else + nIndent = 0; + + // "Shrink to fit" + + bShrink = pPattern->GetItem( ATTR_SHRINKTOFIT, pCondSet ).GetValue(); + + // at least the text size needs to be retrieved again + //! differentiate and do not get the text again from the number format? + maLastCell.clear(); +} + +void ScDrawStringsVars::SetPatternSimple( const ScPatternAttr* pNew, const SfxItemSet* pSet ) +{ + nMaxDigitWidth = 0; + nSignWidth = 0; + nDotWidth = 0; + nExpWidth = 0; + + // Is called, when the font variables do not change (!StringDiffer) + + pPattern = pNew; + pCondSet = pSet; //! is this needed ??? + + // number format + + sal_uLong nOld = nValueFormat; + nValueFormat = pPattern->GetNumberFormat( pOutput->mpDoc->GetFormatTable(), pCondSet ); + + if (nValueFormat != nOld) + maLastCell.clear(); // always reformat + + // margins + + pMargin = &pPattern->GetItem( ATTR_MARGIN, pCondSet ); + + if ( eAttrHorJust == SvxCellHorJustify::Left ) + nIndent = pPattern->GetItem( ATTR_INDENT, pCondSet ).GetValue(); + else + nIndent = 0; + + // "Shrink to fit" + + bShrink = pPattern->GetItem( ATTR_SHRINKTOFIT, pCondSet ).GetValue(); +} + +static bool SameValue( const ScRefCellValue& rCell, const ScRefCellValue& rOldCell ) +{ + return rOldCell.meType == CELLTYPE_VALUE && rCell.meType == CELLTYPE_VALUE && + rCell.mfValue == rOldCell.mfValue; +} + +bool ScDrawStringsVars::SetText( const ScRefCellValue& rCell ) +{ + bool bChanged = false; + + if (!rCell.isEmpty()) + { + if (!SameValue(rCell, maLastCell)) + { + maLastCell = rCell; // store cell + + const Color* pColor; + sal_uLong nFormat = nValueFormat; + aString = ScCellFormat::GetString( rCell, + nFormat, &pColor, + *pOutput->mpDoc->GetFormatTable(), + *pOutput->mpDoc, + pOutput->mbShowNullValues, + pOutput->mbShowFormulas, + true ); + if ( nFormat ) + { + nRepeatPos = aString.indexOf( 0x1B ); + if ( nRepeatPos != -1 ) + { + if (nRepeatPos + 1 == aString.getLength()) + nRepeatPos = -1; + else + { + nRepeatChar = aString[ nRepeatPos + 1 ]; + // delete placeholder and char to repeat + aString = aString.replaceAt( nRepeatPos, 2, u"" ); + // Do not cache/reuse a repeat-filled string, column + // widths or fonts or sizes may differ. + maLastCell.clear(); + } + } + } + else + { + nRepeatPos = -1; + nRepeatChar = 0x0; + } + if (aString.getLength() > DRAWTEXT_MAX) + aString = aString.copy(0, DRAWTEXT_MAX); + + if ( pColor && !pOutput->mbSyntaxMode && !( pOutput->mbUseStyleColor && pOutput->mbForceAutoColor ) ) + { + OutputDevice* pDev = pOutput->mpDev; + aFont.SetColor(*pColor); + pDev->SetFont( aFont ); // only for output + bChanged = true; + maLastCell.clear(); // next time return here again + } + + TextChanged(); + } + // otherwise keep string/size + } + else + { + aString.clear(); + maLastCell.clear(); + aTextSize = Size(0,0); + nOriginalWidth = 0; + } + + return bChanged; +} + +void ScDrawStringsVars::SetHashText() +{ + SetAutoText("###"); +} + +void ScDrawStringsVars::RepeatToFill( tools::Long nColWidth ) +{ + if ( nRepeatPos == -1 || nRepeatPos > aString.getLength() ) + return; + + tools::Long nCharWidth = GetFmtTextWidth(OUString(nRepeatChar)); + + 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(); + } + + tools::Long nSpaceToFill = nColWidth - nTextWidth; + if ( nSpaceToFill <= nCharWidth ) + return; + + sal_Int32 nCharsToInsert = nSpaceToFill / nCharWidth; + OUStringBuffer aFill(nCharsToInsert); + comphelper::string::padToLength(aFill, nCharsToInsert, nRepeatChar); + aString = aString.replaceAt( nRepeatPos, 0, aFill ); + TextChanged(); +} + +void ScDrawStringsVars::SetTextToWidthOrHash( ScRefCellValue& rCell, tools::Long nWidth ) +{ + // #i113045# do the single-character width calculations in logic units + if (bPixelToLogic) + nWidth = pOutput->mpRefDevice->PixelToLogic(Size(nWidth,0)).Width(); + + CellType eType = rCell.meType; + if (eType != CELLTYPE_VALUE && eType != CELLTYPE_FORMULA) + // must be a value or formula cell. + return; + + if (eType == CELLTYPE_FORMULA) + { + ScFormulaCell* pFCell = rCell.mpFormula; + if (pFCell->GetErrCode() != FormulaError::NONE || pOutput->mbShowFormulas) + { + SetHashText(); // If the error string doesn't fit, always use "###". Also for "display formulas" (#i116691#) + return; + } + // If it's formula, the result must be a value. + if (!pFCell->IsValue()) + return; + } + + sal_uLong nFormat = GetResultValueFormat(); + if ((nFormat % SV_COUNTRY_LANGUAGE_OFFSET) != 0) + { + // Not 'General' number format. Set hash text and bail out. + SetHashText(); + return; + } + + double fVal = rCell.getValue(); + + const SvNumberformat* pNumFormat = pOutput->mpDoc->GetFormatTable()->GetEntry(nFormat); + if (!pNumFormat) + return; + + tools::Long nMaxDigit = GetMaxDigitWidth(); + if (!nMaxDigit) + return; + + sal_uInt16 nNumDigits = static_cast<sal_uInt16>(nWidth / nMaxDigit); + { + OUString sTempOut(aString); + if (!pNumFormat->GetOutputString(fVal, nNumDigits, sTempOut)) + { + aString = sTempOut; + // Failed to get output string. Bail out. + return; + } + aString = sTempOut; + } + sal_uInt8 nSignCount = 0, nDecimalCount = 0, nExpCount = 0; + sal_Int32 nLen = aString.getLength(); + sal_Unicode cDecSep = ScGlobal::getLocaleData().getLocaleItem().decimalSeparator[0]; + for( sal_Int32 i = 0; i < nLen; ++i ) + { + sal_Unicode c = aString[i]; + if (c == '-') + ++nSignCount; + else if (c == cDecSep) + ++nDecimalCount; + else if (c == 'E') + ++nExpCount; + } + + // #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)) + { + aString = sTempOut; + // Failed to get output string. Bail out. + return; + } + aString = sTempOut; + } + + tools::Long nActualTextWidth = GetFmtTextWidth(aString); + if (nActualTextWidth > nWidth) + { + // Even after the decimal adjustment the text doesn't fit. Give up. + SetHashText(); + return; + } + + TextChanged(); + maLastCell.clear(); // #i113022# equal cell and format in another column may give different string +} + +void ScDrawStringsVars::SetAutoText( const OUString& rAutoText ) +{ + aString = rAutoText; + + OutputDevice* pRefDevice = pOutput->mpRefDevice; + OutputDevice* pFmtDevice = pOutput->pFmtDevice; + aTextSize.setWidth( GetFmtTextWidth( aString ) ); + aTextSize.setHeight( pFmtDevice->GetTextHeight() ); + + if ( !pRefDevice->GetConnectMetaFile() || pRefDevice->GetOutDevType() == OUTDEV_PRINTER ) + { + double fMul = pOutput->GetStretch(); + aTextSize.setWidth( static_cast<tools::Long>(aTextSize.Width() / fMul + 0.5) ); + } + + aTextSize.setHeight( aMetric.GetAscent() + aMetric.GetDescent() ); + if ( GetOrient() != SvxCellOrientation::Standard ) + { + tools::Long nTemp = aTextSize.Height(); + aTextSize.setHeight( aTextSize.Width() ); + aTextSize.setWidth( nTemp ); + } + + nOriginalWidth = aTextSize.Width(); + if ( bPixelToLogic ) + aTextSize = pRefDevice->LogicToPixel( aTextSize ); + + 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; + + nSignWidth = pOutput->pFmtDevice->GetTextWidth(OUString('-')); + return nSignWidth; +} + +tools::Long ScDrawStringsVars::GetDotWidth() +{ + if (nDotWidth > 0) + return nDotWidth; + + const OUString& sep = ScGlobal::getLocaleData().getLocaleItem().decimalSeparator; + nDotWidth = pOutput->pFmtDevice->GetTextWidth(sep); + return nDotWidth; +} + +tools::Long ScDrawStringsVars::GetExpWidth() +{ + if (nExpWidth > 0) + return nExpWidth; + + nExpWidth = pOutput->pFmtDevice->GetTextWidth(OUString('E')); + return nExpWidth; +} + +tools::Long ScDrawStringsVars::GetFmtTextWidth( const OUString& rString ) +{ + return pOutput->pFmtDevice->GetTextWidth( rString, 0, -1, nullptr, GetLayoutGlyphs( rString )); +} + +void ScDrawStringsVars::TextChanged() +{ + OutputDevice* pRefDevice = pOutput->mpRefDevice; + OutputDevice* pFmtDevice = pOutput->pFmtDevice; + aTextSize.setWidth( GetFmtTextWidth( aString ) ); + aTextSize.setHeight( pFmtDevice->GetTextHeight() ); + + if ( !pRefDevice->GetConnectMetaFile() || pRefDevice->GetOutDevType() == OUTDEV_PRINTER ) + { + double fMul = pOutput->GetStretch(); + aTextSize.setWidth( static_cast<tools::Long>(aTextSize.Width() / fMul + 0.5) ); + } + + aTextSize.setHeight( aMetric.GetAscent() + aMetric.GetDescent() ); + if ( GetOrient() != SvxCellOrientation::Standard ) + { + tools::Long nTemp = aTextSize.Height(); + aTextSize.setHeight( aTextSize.Width() ); + aTextSize.setWidth( nTemp ); + } + + nOriginalWidth = aTextSize.Width(); + if ( bPixelToLogic ) + aTextSize = pRefDevice->LogicToPixel( aTextSize ); +} + +bool ScDrawStringsVars::HasEditCharacters() const +{ + for (sal_Int32 nIdx = 0; nIdx < aString.getLength(); ++nIdx) + { + switch(aString[nIdx]) + { + case CHAR_NBSP: + case CHAR_SHY: + case CHAR_ZWSP: + case CHAR_LRM: + case CHAR_RLM: + case CHAR_NBHY: + case CHAR_WJ: + return true; + default: + break; + } + } + + return false; +} + +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(); + return static_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 + return static_cast<double>(aZoomY) / static_cast<double>(aZoomX); + } +} + +// output strings + +static void lcl_DoHyperlinkResult( const OutputDevice* pDev, const tools::Rectangle& rRect, ScRefCellValue& rCell ) +{ + vcl::PDFExtOutDevData* pPDFData = dynamic_cast< vcl::PDFExtOutDevData* >( pDev->GetExtOutDevData() ); + + OUString aURL; + OUString aCellText; + if (rCell.meType == CELLTYPE_FORMULA) + { + ScFormulaCell* pFCell = rCell.mpFormula; + if ( pFCell->IsHyperLinkCell() ) + pFCell->GetURLResult( aURL, aCellText ); + } + + if ( !aURL.isEmpty() && pPDFData ) + { + vcl::PDFExtOutDevBookmarkEntry aBookmark; + aBookmark.nLinkId = pPDFData->CreateLink(rRect, aCellText); + aBookmark.aBookmark = aURL; + std::vector< vcl::PDFExtOutDevBookmarkEntry >& rBookmarks = pPDFData->GetBookmarks(); + rBookmarks.push_back( aBookmark ); + } +} + +void ScOutputData::SetSyntaxColor( vcl::Font* pFont, const ScRefCellValue& rCell ) +{ + switch (rCell.meType) + { + case CELLTYPE_VALUE: + pFont->SetColor(*mxValueColor); + break; + case CELLTYPE_STRING: + pFont->SetColor(*mxTextColor); + break; + case CELLTYPE_FORMULA: + pFont->SetColor(*mxFormulaColor); + break; + default: + { + // added to avoid warnings + } + } +} + +static void lcl_SetEditColor( EditEngine& rEngine, const Color& rColor ) +{ + ESelection aSel( 0, 0, rEngine.GetParagraphCount(), 0 ); + SfxItemSet aSet( rEngine.GetEmptyItemSet() ); + aSet.Put( SvxColorItem( rColor, EE_CHAR_COLOR ) ); + rEngine.QuickSetAttribs( aSet, aSel ); + // function is called with update mode set to FALSE +} + +void ScOutputData::SetEditSyntaxColor( EditEngine& rEngine, const ScRefCellValue& rCell ) +{ + Color aColor; + switch (rCell.meType) + { + case CELLTYPE_VALUE: + aColor = *mxValueColor; + break; + case CELLTYPE_STRING: + aColor = *mxTextColor; + break; + case CELLTYPE_FORMULA: + aColor = *mxFormulaColor; + break; + default: + { + // added to avoid warnings + } + } + lcl_SetEditColor( rEngine, aColor ); +} + +bool ScOutputData::GetMergeOrigin( SCCOL nX, SCROW nY, SCSIZE nArrY, + SCCOL& rOverX, SCROW& rOverY, + bool bVisRowChanged ) +{ + bool bDoMerge = false; + bool bIsLeft = ( nX == nVisX1 ); + bool bIsTop = ( nY == nVisY1 ) || bVisRowChanged; + + bool bHOver; + bool bVOver; + bool bHidden; + + if (!mpDoc->ColHidden(nX, nTab) && nX >= nX1 && nX <= nX2 + && !mpDoc->RowHidden(nY, nTab) && nY >= nY1 && nY <= nY2) + { + ScCellInfo* pInfo = &pRowInfo[nArrY].cellInfo(nX); + bHOver = pInfo->bHOverlapped; + bVOver = pInfo->bVOverlapped; + } + else + { + ScMF nOverlap2 = mpDoc->GetAttr(nX, nY, nTab, ATTR_MERGE_FLAG)->GetValue(); + bHOver = bool(nOverlap2 & ScMF::Hor); + bVOver = bool(nOverlap2 & ScMF::Ver); + } + + if ( bHOver && bVOver ) + bDoMerge = bIsLeft && bIsTop; + else if ( bHOver ) + bDoMerge = bIsLeft; + else if ( bVOver ) + bDoMerge = bIsTop; + + rOverX = nX; + rOverY = nY; + + while (bHOver) // nY constant + { + --rOverX; + bHidden = mpDoc->ColHidden(rOverX, nTab); + if ( !bDoMerge && !bHidden ) + return false; + + if (rOverX >= nX1 && !bHidden) + { + bHOver = pRowInfo[nArrY].cellInfo(rOverX).bHOverlapped; + bVOver = pRowInfo[nArrY].cellInfo(rOverX).bVOverlapped; + } + else + { + ScMF nOverlap = mpDoc->GetAttr(rOverX, rOverY, nTab, ATTR_MERGE_FLAG)->GetValue(); + bHOver = bool(nOverlap & ScMF::Hor); + bVOver = bool(nOverlap & ScMF::Ver); + } + } + + while (bVOver) + { + --rOverY; + bHidden = mpDoc->RowHidden(rOverY, nTab); + if ( !bDoMerge && !bHidden ) + return false; + + if (nArrY>0) + --nArrY; // local copy ! + + if (rOverX >= nX1 && rOverY >= nY1 && + !mpDoc->ColHidden(rOverX, nTab) && + !mpDoc->RowHidden(rOverY, nTab) && + pRowInfo[nArrY].nRowNo == rOverY) + { + bVOver = pRowInfo[nArrY].cellInfo(rOverX).bVOverlapped; + } + else + { + ScMF nOverlap = mpDoc->GetAttr( rOverX, rOverY, nTab, ATTR_MERGE_FLAG )->GetValue(); + bVOver = bool(nOverlap & ScMF::Ver); + } + } + + return true; +} + +static bool StringDiffer( const ScPatternAttr*& rpOldPattern, const ScPatternAttr* pNewPattern ) +{ + OSL_ENSURE( pNewPattern, "pNewPattern" ); + + if ( pNewPattern == rpOldPattern ) + return false; + else if ( !rpOldPattern ) + return true; + else if ( &pNewPattern->GetItem( ATTR_FONT ) != &rpOldPattern->GetItem( ATTR_FONT ) ) + return true; + else if ( &pNewPattern->GetItem( ATTR_CJK_FONT ) != &rpOldPattern->GetItem( ATTR_CJK_FONT ) ) + return true; + else if ( &pNewPattern->GetItem( ATTR_CTL_FONT ) != &rpOldPattern->GetItem( ATTR_CTL_FONT ) ) + return true; + else if ( &pNewPattern->GetItem( ATTR_FONT_HEIGHT ) != &rpOldPattern->GetItem( ATTR_FONT_HEIGHT ) ) + return true; + else if ( &pNewPattern->GetItem( ATTR_CJK_FONT_HEIGHT ) != &rpOldPattern->GetItem( ATTR_CJK_FONT_HEIGHT ) ) + return true; + else if ( &pNewPattern->GetItem( ATTR_CTL_FONT_HEIGHT ) != &rpOldPattern->GetItem( ATTR_CTL_FONT_HEIGHT ) ) + return true; + else if ( &pNewPattern->GetItem( ATTR_FONT_WEIGHT ) != &rpOldPattern->GetItem( ATTR_FONT_WEIGHT ) ) + return true; + else if ( &pNewPattern->GetItem( ATTR_CJK_FONT_WEIGHT ) != &rpOldPattern->GetItem( ATTR_CJK_FONT_WEIGHT ) ) + return true; + else if ( &pNewPattern->GetItem( ATTR_CTL_FONT_WEIGHT ) != &rpOldPattern->GetItem( ATTR_CTL_FONT_WEIGHT ) ) + return true; + else if ( &pNewPattern->GetItem( ATTR_FONT_POSTURE ) != &rpOldPattern->GetItem( ATTR_FONT_POSTURE ) ) + return true; + else if ( &pNewPattern->GetItem( ATTR_CJK_FONT_POSTURE ) != &rpOldPattern->GetItem( ATTR_CJK_FONT_POSTURE ) ) + return true; + else if ( &pNewPattern->GetItem( ATTR_CTL_FONT_POSTURE ) != &rpOldPattern->GetItem( ATTR_CTL_FONT_POSTURE ) ) + return true; + else if ( &pNewPattern->GetItem( ATTR_FONT_UNDERLINE ) != &rpOldPattern->GetItem( ATTR_FONT_UNDERLINE ) ) + return true; + else if ( &pNewPattern->GetItem( ATTR_FONT_OVERLINE ) != &rpOldPattern->GetItem( ATTR_FONT_OVERLINE ) ) + return true; + else if ( &pNewPattern->GetItem( ATTR_FONT_WORDLINE ) != &rpOldPattern->GetItem( ATTR_FONT_WORDLINE ) ) + return true; + else if ( &pNewPattern->GetItem( ATTR_FONT_CROSSEDOUT ) != &rpOldPattern->GetItem( ATTR_FONT_CROSSEDOUT ) ) + return true; + else if ( &pNewPattern->GetItem( ATTR_FONT_CONTOUR ) != &rpOldPattern->GetItem( ATTR_FONT_CONTOUR ) ) + return true; + else if ( &pNewPattern->GetItem( ATTR_FONT_SHADOWED ) != &rpOldPattern->GetItem( ATTR_FONT_SHADOWED ) ) + return true; + else if ( &pNewPattern->GetItem( ATTR_FONT_COLOR ) != &rpOldPattern->GetItem( ATTR_FONT_COLOR ) ) + return true; + else if ( &pNewPattern->GetItem( ATTR_HOR_JUSTIFY ) != &rpOldPattern->GetItem( ATTR_HOR_JUSTIFY ) ) + return true; + else if ( &pNewPattern->GetItem( ATTR_HOR_JUSTIFY_METHOD ) != &rpOldPattern->GetItem( ATTR_HOR_JUSTIFY_METHOD ) ) + return true; + else if ( &pNewPattern->GetItem( ATTR_VER_JUSTIFY ) != &rpOldPattern->GetItem( ATTR_VER_JUSTIFY ) ) + return true; + else if ( &pNewPattern->GetItem( ATTR_VER_JUSTIFY_METHOD ) != &rpOldPattern->GetItem( ATTR_VER_JUSTIFY_METHOD ) ) + return true; + else if ( &pNewPattern->GetItem( ATTR_STACKED ) != &rpOldPattern->GetItem( ATTR_STACKED ) ) + return true; + else if ( &pNewPattern->GetItem( ATTR_LINEBREAK ) != &rpOldPattern->GetItem( ATTR_LINEBREAK ) ) + return true; + else if ( &pNewPattern->GetItem( ATTR_MARGIN ) != &rpOldPattern->GetItem( ATTR_MARGIN ) ) + return true; + else if ( &pNewPattern->GetItem( ATTR_ROTATE_VALUE ) != &rpOldPattern->GetItem( ATTR_ROTATE_VALUE ) ) + return true; + else if ( &pNewPattern->GetItem( ATTR_FORBIDDEN_RULES ) != &rpOldPattern->GetItem( ATTR_FORBIDDEN_RULES ) ) + return true; + else if ( &pNewPattern->GetItem( ATTR_FONT_EMPHASISMARK ) != &rpOldPattern->GetItem( ATTR_FONT_EMPHASISMARK ) ) + return true; + else if ( &pNewPattern->GetItem( ATTR_FONT_RELIEF ) != &rpOldPattern->GetItem( ATTR_FONT_RELIEF ) ) + return true; + else if ( &pNewPattern->GetItem( ATTR_BACKGROUND ) != &rpOldPattern->GetItem( ATTR_BACKGROUND ) ) + return true; // needed with automatic text color + else + { + rpOldPattern = pNewPattern; + return false; + } +} + +static void lcl_CreateInterpretProgress( bool& bProgress, ScDocument* pDoc, + const ScFormulaCell* pFCell ) +{ + if ( !bProgress && pFCell->GetDirty() ) + { + ScProgress::CreateInterpretProgress( pDoc ); + bProgress = true; + } +} + +static bool IsAmbiguousScript( SvtScriptType nScript ) +{ + return ( nScript != SvtScriptType::LATIN && + nScript != SvtScriptType::ASIAN && + nScript != SvtScriptType::COMPLEX ); +} + +bool ScOutputData::IsEmptyCellText( const RowInfo* pThisRowInfo, SCCOL nX, SCROW nY ) +{ + // pThisRowInfo may be NULL + + bool bEmpty; + if ( pThisRowInfo && nX <= nX2 ) + bEmpty = pThisRowInfo->basicCellInfo(nX).bEmptyCellText; + else + { + ScRefCellValue aCell(*mpDoc, ScAddress(nX, nY, nTab)); + bEmpty = aCell.isEmpty(); + } + + 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 bIsPrint = ( eType == OUTTYPE_PRINTER ); + + if ( bIsPrint || bTabProtected ) + { + const ScProtectionAttr* pAttr = + mpDoc->GetEffItem( nX, nY, nTab, ATTR_PROTECTION ); + if ( bIsPrint && pAttr->GetHidePrint() ) + bEmpty = true; + else if ( bTabProtected ) + { + if ( pAttr->GetHideCell() ) + bEmpty = true; + else if ( mbShowFormulas && pAttr->GetHideFormula() ) + { + if (mpDoc->GetCellType(ScAddress(nX, nY, nTab)) == CELLTYPE_FORMULA) + bEmpty = true; + } + } + } + } + return bEmpty; +} + +void ScOutputData::GetVisibleCell( SCCOL nCol, SCROW nRow, SCTAB nTabP, ScRefCellValue& rCell ) +{ + rCell.assign(*mpDoc, ScAddress(nCol, nRow, nTabP)); + if (!rCell.isEmpty() && IsEmptyCellText(nullptr, nCol, nRow)) + rCell.clear(); +} + +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 + + ScRefCellValue aCell(*mpDoc, ScAddress(nX, nY, nTab)); + if (!aCell.isEmpty() && !IsEmptyCellText(nullptr, nX, nY)) + return false; + + const ScPatternAttr* pPattern = mpDoc->GetPattern( nX, nY, nTab ); + return !(pPattern->GetItem(ATTR_MERGE).IsMerged() || + pPattern->GetItem(ATTR_MERGE_FLAG).IsOverlapped()); +} + +// 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::Long nPosY, + 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]; + + tools::Long nLayoutSign = bLayoutRTL ? -1 : 1; + + tools::Long nCellPosX = nPosX; // find nCellX position, starting at nX/nPosX + SCCOL nCompCol = nX; + while ( nCellX > nCompCol ) + { + //! extra member function for width? + tools::Long nColWidth = ( nCompCol <= nX2 ) ? + pRowInfo[0].basicCellInfo(nCompCol).nWidth : + static_cast<tools::Long>( mpDoc->GetColWidth( nCompCol, nTab ) * mnPPTX ); + nCellPosX += nColWidth * nLayoutSign; + ++nCompCol; + } + while ( nCellX < nCompCol ) + { + --nCompCol; + tools::Long nColWidth = ( nCompCol <= nX2 ) ? + pRowInfo[0].basicCellInfo(nCompCol).nWidth : + static_cast<tools::Long>( mpDoc->GetColWidth( nCompCol, nTab ) * mnPPTX ); + nCellPosX -= nColWidth * nLayoutSign; + } + + tools::Long nCellPosY = nPosY; // find nCellY position, starting at nArrY/nPosY + SCSIZE nCompArr = nArrY; + SCROW nCompRow = pRowInfo[nCompArr].nRowNo; + while ( nCellY > nCompRow ) + { + if ( nCompArr + 1 < nArrCount ) + { + nCellPosY += pRowInfo[nCompArr].nHeight; + ++nCompArr; + nCompRow = pRowInfo[nCompArr].nRowNo; + } + else + { + sal_uInt16 nDocHeight = mpDoc->GetRowHeight( nCompRow, nTab ); + if ( nDocHeight ) + nCellPosY += static_cast<tools::Long>( nDocHeight * mnPPTY ); + ++nCompRow; + } + } + nCellPosY -= mpDoc->GetScaledRowHeight( nCellY, nCompRow-1, nTab, mnPPTY ); + + const ScMergeAttr* pMerge = &rPattern.GetItem( ATTR_MERGE ); + bool bMerged = pMerge->IsMerged(); + tools::Long nMergeCols = pMerge->GetColMerge(); + if ( nMergeCols == 0 ) + nMergeCols = 1; + tools::Long nMergeRows = pMerge->GetRowMerge(); + if ( nMergeRows == 0 ) + nMergeRows = 1; + + tools::Long nMergeSizeX = 0; + for ( tools::Long i=0; i<nMergeCols; i++ ) + { + tools::Long nColWidth = ( nCellX+i <= nX2 ) ? + pRowInfo[0].basicCellInfo(nCellX+i).nWidth : + static_cast<tools::Long>( mpDoc->GetColWidth( sal::static_int_cast<SCCOL>(nCellX+i), nTab ) * mnPPTX ); + nMergeSizeX += nColWidth; + } + tools::Long nMergeSizeY = 0; + short nDirect = 0; + if ( rThisRowInfo.nRowNo == nCellY ) + { + // take first row's height from row info + nMergeSizeY += rThisRowInfo.nHeight; + nDirect = 1; // skip in loop + } + // following rows always from document + nMergeSizeY += mpDoc->GetScaledRowHeight( nCellY+nDirect, nCellY+nMergeRows-1, nTab, mnPPTY); + + --nMergeSizeX; // leave out the grid horizontally, also for alignment (align between grid lines) + + rParam.mnColWidth = nMergeSizeX; // store the actual column width. + rParam.mnLeftClipLength = rParam.mnRightClipLength = 0; + + // construct the rectangles using logical left/right values (justify is called at the end) + + // rAlignRect is the single cell or merged area, used for alignment. + + rParam.maAlignRect.SetLeft( nCellPosX ); + rParam.maAlignRect.SetRight( nCellPosX + ( nMergeSizeX - 1 ) * nLayoutSign ); + rParam.maAlignRect.SetTop( nCellPosY ); + rParam.maAlignRect.SetBottom( nCellPosY + nMergeSizeY - 1 ); + + // rClipRect is all cells that are used for output. + // For merged cells this is the same as rAlignRect, otherwise neighboring cells can also be used. + + rParam.maClipRect = rParam.maAlignRect; + if ( nNeeded > nMergeSizeX ) + { + SvxCellHorJustify eHorJust = static_cast<SvxCellHorJustify>(nHorJustify); + + tools::Long nMissing = nNeeded - nMergeSizeX; + tools::Long nLeftMissing = 0; + tools::Long nRightMissing = 0; + switch ( eHorJust ) + { + case SvxCellHorJustify::Left: + nRightMissing = nMissing; + break; + case SvxCellHorJustify::Right: + nLeftMissing = nMissing; + break; + case SvxCellHorJustify::Center: + nLeftMissing = nMissing / 2; + nRightMissing = nMissing - nLeftMissing; + break; + default: + { + // added to avoid warnings + } + } + + // 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 + + while ( nRightMissing > 0 && nRightX < mpDoc->MaxCol() && ( bOverwrite || IsAvailable( nRightX+1, nCellY ) ) ) + { + ++nRightX; + tools::Long nAdd = static_cast<tools::Long>( mpDoc->GetColWidth( nRightX, nTab ) * mnPPTX ); + nRightMissing -= nAdd; + rParam.maClipRect.AdjustRight(nAdd * nLayoutSign ); + + if ( rThisRowInfo.nRowNo == nCellY && nRightX >= nX1 && nRightX <= nX2 ) + rThisRowInfo.cellInfo(nRightX-1).bHideGrid = true; + } + + while ( nLeftMissing > 0 && nLeftX > 0 && ( bOverwrite || IsAvailable( nLeftX-1, nCellY ) ) ) + { + if ( rThisRowInfo.nRowNo == nCellY && nLeftX >= nX1 && nLeftX <= nX2 ) + rThisRowInfo.cellInfo(nLeftX-1).bHideGrid = true; + + --nLeftX; + tools::Long nAdd = static_cast<tools::Long>( mpDoc->GetColWidth( nLeftX, nTab ) * mnPPTX ); + nLeftMissing -= nAdd; + rParam.maClipRect.AdjustLeft( -(nAdd * nLayoutSign) ); + } + } + + // Set flag and reserve space for clipping mark triangle, + // even if rThisRowInfo isn't for nCellY (merged cells). + if ( nRightMissing > 0 && bMarkClipped && nRightX >= nX1 && nRightX <= nX2 && !bBreak && !bCellIsValue ) + { + rThisRowInfo.cellInfo(nRightX).nClipMark |= ScClipMark::Right; + bAnyClipped = true; + tools::Long nMarkPixel = static_cast<tools::Long>( SC_CLIPMARK_SIZE * mnPPTX ); + rParam.maClipRect.AdjustRight( -(nMarkPixel * nLayoutSign) ); + } + if ( nLeftMissing > 0 && bMarkClipped && nLeftX >= nX1 && nLeftX <= nX2 && !bBreak && !bCellIsValue ) + { + rThisRowInfo.cellInfo(nLeftX).nClipMark |= ScClipMark::Left; + bAnyClipped = true; + tools::Long nMarkPixel = static_cast<tools::Long>( SC_CLIPMARK_SIZE * mnPPTX ); + rParam.maClipRect.AdjustLeft(nMarkPixel * nLayoutSign ); + } + + rParam.mbLeftClip = ( nLeftMissing > 0 ); + rParam.mbRightClip = ( nRightMissing > 0 ); + rParam.mnLeftClipLength = nLeftMissing; + rParam.mnRightClipLength = nRightMissing; + } + else + { + rParam.mbLeftClip = rParam.mbRightClip = false; + + // leave space for AutoFilter on screen + // (for automatic line break: only if not formatting for printer, as in ScColumn::GetNeededSize) + + if ( eType==OUTTYPE_WINDOW && + ( rPattern.GetItem(ATTR_MERGE_FLAG).GetValue() & (ScMF::Auto|ScMF::Button|ScMF::ButtonPopup) ) && + ( !bBreak || mpRefDevice == pFmtDevice ) ) + { + // filter drop-down width depends on row height + double fZoom = mpRefDevice ? static_cast<double>(mpRefDevice->GetMapMode().GetScaleY()) : 1.0; + fZoom = fZoom > 1.0 ? fZoom : 1.0; + const tools::Long nFilter = fZoom * DROPDOWN_BITMAP_SIZE; + bool bFit = ( nNeeded + nFilter <= nMergeSizeX ); + if ( bFit ) + { + // content fits even in the remaining area without the filter button + // -> align within that remaining area + + rParam.maAlignRect.AdjustRight( -(nFilter * nLayoutSign) ); + rParam.maClipRect.AdjustRight( -(nFilter * nLayoutSign) ); + } + } + } + + // justify both rectangles for alignment calculation, use with DrawText etc. + + rParam.maAlignRect.Justify(); + rParam.maClipRect.Justify(); +} + +namespace { + +bool beginsWithRTLCharacter(const OUString& rStr) +{ + if (rStr.isEmpty()) + return false; + + 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: + return true; + default: + ; + } + + return false; +} + +} + +/** 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, const bool 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; + } + else if (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; + else if (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; +} + +void ScOutputData::DrawStrings( bool bPixelToLogic ) +{ + LayoutStrings(bPixelToLogic); +} + +tools::Rectangle ScOutputData::LayoutStrings(bool bPixelToLogic, bool bPaint, const ScAddress &rAddress) +{ + bool bOrigIsInLayoutStrings = mpDoc->IsInLayoutStrings(); + mpDoc->SetLayoutStrings(true); + comphelper::ScopeGuard g([this, bOrigIsInLayoutStrings] { + mpDoc->SetLayoutStrings(bOrigIsInLayoutStrings); + }); + + OSL_ENSURE( mpDev == mpRefDevice || + mpDev->GetMapMode().GetMapUnit() == mpRefDevice->GetMapMode().GetMapUnit(), + "LayoutStrings: different MapUnits ?!?!" ); + + vcl::PDFExtOutDevData* pPDFData = dynamic_cast< vcl::PDFExtOutDevData* >(mpDev->GetExtOutDevData() ); + + sc::IdleSwitch aIdleSwitch(*mpDoc, false); + ScDrawStringsVars aVars( this, bPixelToLogic ); + + bool bProgress = false; + + tools::Long nInitPosX = nScrX; + if ( bLayoutRTL ) + nInitPosX += nMirrorW - 1; // pixels + tools::Long nLayoutSign = bLayoutRTL ? -1 : 1; + + SCCOL nLastContentCol = mpDoc->MaxCol(); + if ( nX2 < mpDoc->MaxCol() ) + nLastContentCol = sal::static_int_cast<SCCOL>( + nLastContentCol - mpDoc->GetEmptyLinesInBlock( nX2+1, nY1, nTab, mpDoc->MaxCol(), nY2, nTab, DIR_RIGHT ) ); + SCCOL nLoopStartX = nX1; + if ( nX1 > 0 ) + --nLoopStartX; // start before nX1 for rest of long text to the left + + // variables for GetOutputArea + OutputAreaParam aAreaParam; + bool bCellIsValue = false; + tools::Long nNeededWidth = 0; + const ScPatternAttr* pPattern = nullptr; + const SfxItemSet* pCondSet = nullptr; + const ScPatternAttr* pOldPattern = nullptr; + const SfxItemSet* pOldCondSet = nullptr; + SvtScriptType nOldScript = SvtScriptType::NONE; + + // 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; + + std::vector<sal_Int32> aDX; + tools::Long nPosY = nScrY; + for (SCSIZE nArrY=1; nArrY+1<nArrCount; nArrY++) + { + RowInfo* pThisRowInfo = &pRowInfo[nArrY]; + SCROW nY = pThisRowInfo->nRowNo; + if ((bPaint && pThisRowInfo->bChanged) || (!bPaint && rAddress.Row() == nY)) + { + tools::Long nPosX = nInitPosX; + if ( nLoopStartX < nX1 ) + nPosX -= pRowInfo[0].basicCellInfo(nLoopStartX).nWidth * nLayoutSign; + for (SCCOL nX=nLoopStartX; nX<=nX2; nX++) + { + bool bMergeEmpty = false; + const ScCellInfo* pInfo = &pThisRowInfo->cellInfo(nX); + bool bEmpty = nX < nX1 || pThisRowInfo->basicCellInfo(nX).bEmptyCellText; + + SCCOL nCellX = nX; // position where the cell really starts + SCROW nCellY = nY; + bool bDoCell = false; + bool bUseEditEngine = false; + + // Part of a merged cell? + + bool bOverlapped = (pInfo->bHOverlapped || pInfo->bVOverlapped); + if ( bOverlapped ) + { + bEmpty = true; + + SCCOL nOverX; // start of the merged cells + SCROW nOverY; + bool bVisChanged = !pRowInfo[nArrY-1].bChanged; + if (GetMergeOrigin( nX,nY, nArrY, nOverX,nOverY, bVisChanged )) + { + nCellX = nOverX; + nCellY = nOverY; + bDoCell = true; + } + else + bMergeEmpty = true; + } + + // Rest of a long text further to the left? + + if ( bEmpty && !bMergeEmpty && nX < nX1 && !bOverlapped ) + { + SCCOL nTempX=nX1; + while (nTempX > 0 && IsEmptyCellText( pThisRowInfo, nTempX, nY )) + --nTempX; + + if ( nTempX < nX1 && + !IsEmptyCellText( pThisRowInfo, nTempX, nY ) && + !mpDoc->HasAttrib( nTempX,nY,nTab, nX1,nY,nTab, HasAttrFlags::Merged | HasAttrFlags::Overlapped ) ) + { + nCellX = nTempX; + bDoCell = true; + } + } + + // Rest of a long text further to the right? + + if ( bEmpty && !bMergeEmpty && nX == nX2 && !bOverlapped ) + { + // don't have to look further than nLastContentCol + + SCCOL nTempX=nX; + while (nTempX < nLastContentCol && IsEmptyCellText( pThisRowInfo, nTempX, nY )) + ++nTempX; + + if ( nTempX > nX && + !IsEmptyCellText( pThisRowInfo, nTempX, nY ) && + !mpDoc->HasAttrib( nTempX,nY,nTab, nX,nY,nTab, HasAttrFlags::Merged | HasAttrFlags::Overlapped ) ) + { + nCellX = nTempX; + bDoCell = true; + } + } + + // normal visible cell + + if (!bEmpty) + bDoCell = true; + + // don't output the cell that's being edited + + if ( bDoCell && bEditMode && nCellX == nEditCol && nCellY == nEditRow ) + bDoCell = false; + + // 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; + else if (aCell.meType == CELLTYPE_EDIT) + bUseEditEngine = true; + } + + // Check if this cell is mis-spelled. + if (bDoCell && !bUseEditEngine && aCell.meType == 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); + } + else if ( 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; + } + + SvtScriptType nScript = mpDoc->GetCellScriptType( + ScAddress(nCellX, nCellY, nTab), + pPattern->GetNumberFormat(mpDoc->GetFormatTable(), pCondSet)); + + if (nScript == SvtScriptType::NONE) + nScript = ScGlobal::GetDefaultScriptType(); + + if ( pPattern != pOldPattern || pCondSet != pOldCondSet || + nScript != nOldScript || mbSyntaxMode ) + { + if ( StringDiffer(pOldPattern,pPattern) || + pCondSet != pOldCondSet || nScript != nOldScript || mbSyntaxMode ) + { + aVars.SetPattern(pPattern, pCondSet, aCell, nScript); + } + else + aVars.SetPatternSimple( pPattern, pCondSet ); + pOldPattern = pPattern; + pOldCondSet = pCondSet; + nOldScript = nScript; + } + + // use edit engine for rotated, stacked or mixed-script text + if ( aVars.GetOrient() == SvxCellOrientation::Stacked || + aVars.IsRotated() || IsAmbiguousScript(nScript) ) + bUseEditEngine = true; + } + if (bDoCell && !bUseEditEngine) + { + bool bFormulaCell = (aCell.meType == CELLTYPE_FORMULA); + if ( bFormulaCell ) + lcl_CreateInterpretProgress(bProgress, mpDoc, aCell.mpFormula); + if ( aVars.SetText(aCell) ) + pOldPattern = nullptr; + bUseEditEngine = aVars.HasEditCharacters() || (bFormulaCell && aCell.mpFormula->IsMultilineResult()); + } + tools::Long nTotalMargin = 0; + SvxCellHorJustify eOutHorJust = SvxCellHorJustify::Standard; + if (bDoCell && !bUseEditEngine) + { + CellType eCellType = aCell.meType; + bCellIsValue = ( eCellType == CELLTYPE_VALUE ); + if ( eCellType == CELLTYPE_FORMULA ) + { + ScFormulaCell* pFCell = aCell.mpFormula; + bCellIsValue = pFCell->IsRunning() || pFCell->IsValue(); + } + + const bool bNumberFormatIsText = lcl_isNumberFormatText( mpDoc, nCellX, nCellY, nTab ); + eOutHorJust = getAlignmentFromContext( aVars.GetHorJust(), bCellIsValue, aVars.GetString(), + *pPattern, pCondSet, mpDoc, nTab, bNumberFormatIsText ); + + bool bBreak = ( aVars.GetLineBreak() || aVars.GetHorJust() == SvxCellHorJustify::Block ); + // #i111387# #o11817313# tdf#121040 disable automatic line breaks for all number formats + // Must be synchronized with ScColumn::GetNeededSize() + SvNumberFormatter* pFormatter = mpDoc->GetFormatTable(); + if (bBreak && bCellIsValue && (pFormatter->GetType(aVars.GetResultValueFormat()) == SvNumFormatType::NUMBER)) + bBreak = false; + + bool bRepeat = aVars.IsRepeat() && !bBreak; + bool bShrink = aVars.IsShrink() && !bBreak && !bRepeat; + + nTotalMargin = + static_cast<tools::Long>(aVars.GetLeftTotal() * mnPPTX) + + static_cast<tools::Long>(aVars.GetMargin()->GetRightMargin() * mnPPTX); + + nNeededWidth = aVars.GetTextSize().Width() + nTotalMargin; + + // GetOutputArea gives justified rectangles + GetOutputArea( nX, nArrY, nPosX, nPosY, nCellX, nCellY, nNeededWidth, + *pPattern, sal::static_int_cast<sal_uInt16>(eOutHorJust), + bCellIsValue || bRepeat || bShrink, bBreak, false, + aAreaParam ); + + aVars.RepeatToFill( aAreaParam.mnColWidth - nTotalMargin ); + if ( bShrink ) + { + if ( aVars.GetOrient() != SvxCellOrientation::Standard ) + { + // Only horizontal scaling is handled here. + // DrawEdit is used to vertically scale 90 deg rotated text. + bUseEditEngine = true; + } + else if ( aAreaParam.mbLeftClip || aAreaParam.mbRightClip ) // horizontal + { + tools::Long nAvailable = aAreaParam.maAlignRect.GetWidth() - nTotalMargin; + tools::Long nScaleSize = aVars.GetTextSize().Width(); // without margin + + if ( nAvailable > 0 && nScaleSize > 0 ) // 0 if the text is empty (formulas, number formats) + { + tools::Long nScale = ( nAvailable * 100 ) / nScaleSize; + + aVars.SetShrinkScale( nScale, nOldScript ); + tools::Long nNewSize = aVars.GetTextSize().Width(); + + 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; + RowInfo* pMarkRowInfo = ( nCellY == nY ) ? pThisRowInfo : &pRowInfo[0]; + pMarkRowInfo->basicCellInfo(nMarkX).bEditEngine = true; + bDoCell = false; // don't draw here + } + if ( bDoCell ) + { + if ( bCellIsValue && ( aAreaParam.mbLeftClip || aAreaParam.mbRightClip ) ) + { + if (mbShowFormulas) + aVars.SetHashText(); + else + // Adjust the decimals to fit the available column width. + aVars.SetTextToWidthOrHash(aCell, aAreaParam.mnColWidth - nTotalMargin); + + nNeededWidth = aVars.GetTextSize().Width() + + static_cast<tools::Long>( aVars.GetLeftTotal() * mnPPTX ) + + static_cast<tools::Long>( aVars.GetMargin()->GetRightMargin() * mnPPTX ); + if ( nNeededWidth <= aAreaParam.maClipRect.GetWidth() ) + { + // Cell value is no longer clipped. Reset relevant parameters. + aAreaParam.mbLeftClip = aAreaParam.mbRightClip = false; + aAreaParam.mnLeftClipLength = aAreaParam.mnRightClipLength = 0; + } + + // If the "###" replacement doesn't fit into the cells, no clip marks + // are shown, as the "###" already denotes too little space. + // The rectangles from the first GetOutputArea call remain valid. + } + + tools::Long nJustPosX = aAreaParam.maAlignRect.Left(); // "justified" - effect of alignment will be added + tools::Long nJustPosY = aAreaParam.maAlignRect.Top(); + tools::Long nAvailWidth = aAreaParam.maAlignRect.GetWidth(); + tools::Long nOutHeight = aAreaParam.maAlignRect.GetHeight(); + + bool bOutside = ( aAreaParam.maClipRect.Right() < nScrX || aAreaParam.maClipRect.Left() >= nScrX + nScrW ); + // Take adjusted values of aAreaParam.mbLeftClip and aAreaParam.mbRightClip + bool bVClip = AdjustAreaParamClipRect(aAreaParam); + bool bHClip = aAreaParam.mbLeftClip || aAreaParam.mbRightClip; + + // check horizontal space + + if ( !bOutside ) + { + bool bRightAdjusted = false; // to correct text width calculation later + switch (eOutHorJust) + { + case SvxCellHorJustify::Left: + nJustPosX += static_cast<tools::Long>( aVars.GetLeftTotal() * mnPPTX ); + break; + case SvxCellHorJustify::Right: + nJustPosX += nAvailWidth - aVars.GetTextSize().Width() - + static_cast<tools::Long>( aVars.GetRightTotal() * mnPPTX ); + bRightAdjusted = true; + break; + case SvxCellHorJustify::Center: + nJustPosX += ( nAvailWidth - aVars.GetTextSize().Width() + + static_cast<tools::Long>( aVars.GetLeftTotal() * mnPPTX ) - + static_cast<tools::Long>( aVars.GetMargin()->GetRightMargin() * mnPPTX ) ) / 2; + break; + default: + { + // added to avoid warnings + } + } + + tools::Long nTestClipHeight = aVars.GetTextSize().Height(); + switch (aVars.GetVerJust()) + { + case SvxCellVerJustify::Top: + case SvxCellVerJustify::Block: + { + tools::Long nTop = static_cast<tools::Long>( aVars.GetMargin()->GetTopMargin() * mnPPTY ); + nJustPosY += nTop; + nTestClipHeight += nTop; + } + break; + case SvxCellVerJustify::Bottom: + { + tools::Long nBot = static_cast<tools::Long>( aVars.GetMargin()->GetBottomMargin() * mnPPTY ); + nJustPosY += nOutHeight - aVars.GetTextSize().Height() - nBot; + nTestClipHeight += nBot; + } + break; + case SvxCellVerJustify::Center: + { + tools::Long nTop = static_cast<tools::Long>( aVars.GetMargin()->GetTopMargin() * mnPPTY ); + tools::Long nBot = static_cast<tools::Long>( aVars.GetMargin()->GetBottomMargin() * mnPPTY ); + nJustPosY += ( nOutHeight + nTop - + aVars.GetTextSize().Height() - nBot ) / 2; + nTestClipHeight += std::abs( nTop - nBot ); + } + break; + default: + { + // added to avoid warnings + } + } + + 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 ); + + if (bMetaFile) + { + mpDev->Push(); + mpDev->IntersectClipRegion( aAreaParam.maClipRect ); + } + else + mpDev->SetClipRegion( vcl::Region( 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() ); + + aDrawTextPos = mpRefDevice->PixelToLogic( aDrawTextPos ); + + // redo text width adjustment in logic units + if (bRightAdjusted) + aDrawTextPos.AdjustX( -(aVars.GetOriginalWidth()) ); + } + + // in Metafiles always use DrawTextArray to ensure that positions are + // recorded (for non-proportional resize): + + const OUString& aString = aVars.GetString(); + if (!aString.isEmpty()) + { + // 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); + } + } + else if (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 we are not painting, it means we are interested in + // the area of the text that covers the specified cell + if (!bPaint && rAddress.Col() == nX) + { + tools::Rectangle aRect; + mpDev->GetTextBoundRect(aRect, aShort); + aRect += aDrawTextPos; + return aRect; + } + + if (bMetaFile || pFmtDevice != mpDev || aZoomX != aZoomY) + { + size_t nLen = aShort.getLength(); + if (aDX.size() < nLen) + aDX.resize(nLen, 0); + + pFmtDevice->GetTextArray(aShort, &aDX); + + if ( !mpRefDevice->GetConnectMetaFile() || + mpRefDevice->GetOutDevType() == OUTDEV_PRINTER ) + { + double fMul = GetStretch(); + for (size_t i = 0; i < nLen; ++i) + aDX[i] = static_cast<sal_Int32>(aDX[i] / fMul + 0.5); + } + + if (bPaint) + mpDev->DrawTextArray(aDrawTextPos, aShort, aDX); + } + else + { + if (bPaint) + mpDev->DrawText(aDrawTextPos, aShort, 0, -1, nullptr, nullptr, + aVars.GetLayoutGlyphs(aShort)); + } + } + + if ( bHClip || bVClip ) + { + if (bMetaFile) + mpDev->Pop(); + else + mpDev->SetClipRegion(); + } + + // PDF: whole-cell hyperlink from formula? + bool bHasURL = pPDFData && aCell.meType == CELLTYPE_FORMULA && aCell.mpFormula->IsHyperLinkCell(); + if (bPaint && bHasURL) + { + tools::Rectangle aURLRect( aURLStart, aVars.GetTextSize() ); + lcl_DoHyperlinkResult(mpDev, aURLRect, aCell); + } + } + } + nPosX += pRowInfo[0].basicCellInfo(nX).nWidth * nLayoutSign; + } + } + nPosY += pRowInfo[nArrY].nHeight; + } + if ( bProgress ) + ScProgress::DeleteInterpretProgress(); + + return tools::Rectangle(); +} + +std::unique_ptr<ScFieldEditEngine> ScOutputData::CreateOutputEditEngine() +{ + std::unique_ptr<ScFieldEditEngine> pEngine(new ScFieldEditEngine(mpDoc, mpDoc->GetEnginePool())); + pEngine->SetUpdateLayout( false ); + // a RefDevice always has to be set, otherwise EditEngine would create a VirtualDevice + pEngine->SetRefDevice( pFmtDevice ); + EEControlBits nCtrl = pEngine->GetControlWord(); + if ( bShowSpellErrors ) + nCtrl |= EEControlBits::ONLINESPELLING; + if ( eType == OUTTYPE_PRINTER ) + nCtrl &= ~EEControlBits::MARKFIELDS; + else + nCtrl &= ~EEControlBits::MARKURLFIELDS; // URLs not shaded for output + if ( eType == OUTTYPE_WINDOW && mpRefDevice == pFmtDevice ) + nCtrl &= ~EEControlBits::FORMAT100; // use the actual MapMode + pEngine->SetControlWord( nCtrl ); + mpDoc->ApplyAsianEditSettings( *pEngine ); + pEngine->EnableAutoColor( mbUseStyleColor ); + pEngine->SetDefaultHorizontalTextDirection( mpDoc->GetEditTextDirection( nTab ) ); + return pEngine; +} + +static void 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); +} + +static bool lcl_SafeIsValue( ScRefCellValue& rCell ) +{ + switch (rCell.meType) + { + case CELLTYPE_VALUE: + return true; + case CELLTYPE_FORMULA: + { + ScFormulaCell* pFCell = rCell.mpFormula; + if (pFCell->IsRunning() || pFCell->IsValue()) + return true; + } + break; + default: + { + // added to avoid warnings + } + } + return false; +} + +static void lcl_ScaleFonts( EditEngine& rEngine, tools::Long nPercent ) +{ + bool bUpdateMode = rEngine.SetUpdateLayout( false ); + + sal_Int32 nParCount = rEngine.GetParagraphCount(); + for (sal_Int32 nPar=0; nPar<nParCount; nPar++) + { + std::vector<sal_Int32> aPortions; + rEngine.GetPortions( nPar, aPortions ); + + sal_Int32 nStart = 0; + for ( const sal_Int32 nEnd : aPortions ) + { + ESelection aSel( nPar, nStart, nPar, nEnd ); + SfxItemSet aAttribs = rEngine.GetAttribs( aSel ); + + tools::Long nWestern = aAttribs.Get(EE_CHAR_FONTHEIGHT).GetHeight(); + tools::Long nCJK = aAttribs.Get(EE_CHAR_FONTHEIGHT_CJK).GetHeight(); + tools::Long nCTL = aAttribs.Get(EE_CHAR_FONTHEIGHT_CTL).GetHeight(); + + nWestern = ( nWestern * nPercent ) / 100; + nCJK = ( nCJK * nPercent ) / 100; + nCTL = ( nCTL * nPercent ) / 100; + + aAttribs.Put( SvxFontHeightItem( nWestern, 100, EE_CHAR_FONTHEIGHT ) ); + aAttribs.Put( SvxFontHeightItem( nCJK, 100, EE_CHAR_FONTHEIGHT_CJK ) ); + aAttribs.Put( SvxFontHeightItem( nCTL, 100, EE_CHAR_FONTHEIGHT_CTL ) ); + + rEngine.QuickSetAttribs( aAttribs, aSel ); //! remove paragraph attributes from aAttribs? + + nStart = nEnd; + } + } + + if ( bUpdateMode ) + rEngine.SetUpdateLayout( true ); +} + +static tools::Long lcl_GetEditSize( EditEngine& rEngine, bool bWidth, bool bSwap, Degree100 nAttrRotate ) +{ + if ( bSwap ) + bWidth = !bWidth; + + if ( nAttrRotate ) + { + tools::Long nRealWidth = static_cast<tools::Long>(rEngine.CalcTextWidth()); + tools::Long nRealHeight = rEngine.GetTextHeight(); + + // assuming standard mode, otherwise width isn't used + + double nRealOrient = toRadians(nAttrRotate); // 1/100th degrees + double nAbsCos = fabs( cos( nRealOrient ) ); + double nAbsSin = fabs( sin( nRealOrient ) ); + if ( bWidth ) + return static_cast<tools::Long>( nRealWidth * nAbsCos + nRealHeight * nAbsSin ); + else + return static_cast<tools::Long>( nRealHeight * nAbsCos + nRealWidth * nAbsSin ); + } + else if ( bWidth ) + return static_cast<tools::Long>(rEngine.CalcTextWidth()); + else + return rEngine.GetTextHeight(); +} + +void ScOutputData::ShrinkEditEngine( EditEngine& rEngine, const tools::Rectangle& rAlignRect, + tools::Long nLeftM, tools::Long nTopM, tools::Long nRightM, tools::Long nBottomM, + bool bWidth, SvxCellOrientation nOrient, Degree100 nAttrRotate, bool bPixelToLogic, + tools::Long& rEngineWidth, tools::Long& rEngineHeight, tools::Long& rNeededPixel, bool& rLeftClip, bool& rRightClip ) +{ + if ( !bWidth ) + { + // vertical + + tools::Long nScaleSize = bPixelToLogic ? + mpRefDevice->LogicToPixel(Size(0,rEngineHeight)).Height() : rEngineHeight; + + // Don't scale if it fits already. + // Allowing to extend into the margin, to avoid scaling at optimal height. + if ( nScaleSize <= rAlignRect.GetHeight() ) + return; + + bool bSwap = ( nOrient == SvxCellOrientation::TopBottom || nOrient == SvxCellOrientation::BottomUp ); + tools::Long nAvailable = rAlignRect.GetHeight() - nTopM - nBottomM; + tools::Long nScale = ( nAvailable * 100 ) / nScaleSize; + + lcl_ScaleFonts( rEngine, nScale ); + rEngineHeight = lcl_GetEditSize( rEngine, false, bSwap, nAttrRotate ); + tools::Long nNewSize = bPixelToLogic ? + mpRefDevice->LogicToPixel(Size(0,rEngineHeight)).Height() : rEngineHeight; + + sal_uInt16 nShrinkAgain = 0; + while ( nNewSize > nAvailable && nShrinkAgain < SC_SHRINKAGAIN_MAX ) + { + // further reduce, like in DrawStrings + lcl_ScaleFonts( rEngine, 90 ); // reduce by 10% + rEngineHeight = lcl_GetEditSize( rEngine, false, bSwap, nAttrRotate ); + nNewSize = bPixelToLogic ? + mpRefDevice->LogicToPixel(Size(0,rEngineHeight)).Height() : rEngineHeight; + ++nShrinkAgain; + } + + // sizes for further processing (alignment etc): + rEngineWidth = lcl_GetEditSize( rEngine, true, bSwap, nAttrRotate ); + tools::Long nPixelWidth = bPixelToLogic ? + mpRefDevice->LogicToPixel(Size(rEngineWidth,0)).Width() : rEngineWidth; + rNeededPixel = nPixelWidth + nLeftM + nRightM; + } + else if ( rLeftClip || rRightClip ) + { + // horizontal + + tools::Long nAvailable = rAlignRect.GetWidth() - nLeftM - nRightM; + tools::Long nScaleSize = rNeededPixel - nLeftM - nRightM; // without margin + + if ( nScaleSize <= nAvailable ) + return; + + tools::Long nScale = ( nAvailable * 100 ) / nScaleSize; + + lcl_ScaleFonts( rEngine, nScale ); + rEngineWidth = lcl_GetEditSize( rEngine, true, false, nAttrRotate ); + tools::Long nNewSize = bPixelToLogic ? + mpRefDevice->LogicToPixel(Size(rEngineWidth,0)).Width() : rEngineWidth; + + sal_uInt16 nShrinkAgain = 0; + while ( nNewSize > nAvailable && nShrinkAgain < SC_SHRINKAGAIN_MAX ) + { + // further reduce, like in DrawStrings + lcl_ScaleFonts( rEngine, 90 ); // reduce by 10% + rEngineWidth = lcl_GetEditSize( rEngine, true, false, nAttrRotate ); + nNewSize = bPixelToLogic ? + mpRefDevice->LogicToPixel(Size(rEngineWidth,0)).Width() : rEngineWidth; + ++nShrinkAgain; + } + if ( nNewSize <= nAvailable ) + rLeftClip = rRightClip = false; + + // sizes for further processing (alignment etc): + rNeededPixel = nNewSize + nLeftM + nRightM; + rEngineHeight = lcl_GetEditSize( rEngine, false, false, nAttrRotate ); + } +} + +ScOutputData::DrawEditParam::DrawEditParam(const ScPatternAttr* pPattern, const SfxItemSet* pCondSet, bool bCellIsValue) : + meHorJustAttr( lcl_GetValue<SvxHorJustifyItem, SvxCellHorJustify>(*pPattern, ATTR_HOR_JUSTIFY, pCondSet) ), + meHorJustContext( meHorJustAttr ), + meHorJustResult( meHorJustAttr ), + meVerJust( lcl_GetValue<SvxVerJustifyItem, SvxCellVerJustify>(*pPattern, ATTR_VER_JUSTIFY, pCondSet) ), + meHorJustMethod( lcl_GetValue<SvxJustifyMethodItem, SvxCellJustifyMethod>(*pPattern, ATTR_HOR_JUSTIFY_METHOD, pCondSet) ), + meVerJustMethod( lcl_GetValue<SvxJustifyMethodItem, SvxCellJustifyMethod>(*pPattern, ATTR_VER_JUSTIFY_METHOD, pCondSet) ), + meOrient( pPattern->GetCellOrientation(pCondSet) ), + mnArrY(0), + mnX(0), mnCellX(0), mnCellY(0), + mnPosX(0), mnPosY(0), mnInitPosX(0), + mbBreak( (meHorJustAttr == SvxCellHorJustify::Block) || lcl_GetBoolValue(*pPattern, ATTR_LINEBREAK, pCondSet) ), + mbCellIsValue(bCellIsValue), + mbAsianVertical(false), + mbPixelToLogic(false), + mbHyphenatorSet(false), + mpEngine(nullptr), + mpPattern(pPattern), + mpCondSet(pCondSet), + mpPreviewFontSet(nullptr), + mpOldPattern(nullptr), + mpOldCondSet(nullptr), + mpOldPreviewFontSet(nullptr), + mpThisRowInfo(nullptr), + mpMisspellRanges(nullptr) +{} + +bool ScOutputData::DrawEditParam::readCellContent( + const ScDocument* pDoc, bool bShowNullValues, bool bShowFormulas, bool bSyntaxMode, bool bUseStyleColor, bool bForceAutoColor, bool& rWrapFields) +{ + if (maCell.meType == CELLTYPE_EDIT) + { + const EditTextObject* pData = maCell.mpEditText; + if (pData) + { + mpEngine->SetTextCurrentDefaults(*pData); + + if ( mbBreak && !mbAsianVertical && pData->HasField() ) + { + // Fields aren't wrapped, so clipping is enabled to prevent + // a field from being drawn beyond the cell size + + rWrapFields = true; + } + } + else + { + OSL_FAIL("pData == 0"); + return false; + } + } + else + { + sal_uInt32 nFormat = mpPattern->GetNumberFormat( + pDoc->GetFormatTable(), mpCondSet ); + const Color* pColor; + OUString aString = ScCellFormat::GetString( maCell, + nFormat, &pColor, + *pDoc->GetFormatTable(), + *pDoc, + bShowNullValues, + bShowFormulas); + + mpEngine->SetTextCurrentDefaults(aString); + if ( pColor && !bSyntaxMode && !( bUseStyleColor && bForceAutoColor ) ) + lcl_SetEditColor( *mpEngine, *pColor ); + } + + if (mpMisspellRanges) + mpEngine->SetAllMisspellRanges(*mpMisspellRanges); + + return true; +} + +void ScOutputData::DrawEditParam::setPatternToEngine(bool bUseStyleColor) +{ + // syntax highlighting mode is ignored here + // StringDiffer doesn't look at hyphenate, language items + + if (mpPattern == mpOldPattern && mpCondSet == mpOldCondSet && mpPreviewFontSet == mpOldPreviewFontSet ) + return; + + Color nConfBackColor = SC_MOD()->GetColorConfig().GetColorValue(svtools::DOCCOLOR).nColor; + bool bCellContrast = bUseStyleColor && + Application::GetSettings().GetStyleSettings().GetHighContrastMode(); + + auto pSet = std::make_unique<SfxItemSet>( mpEngine->GetEmptyItemSet() ); + mpPattern->FillEditItemSet( pSet.get(), mpCondSet ); + if ( mpPreviewFontSet ) + { + if ( const SvxFontItem* pItem = mpPreviewFontSet->GetItemIfSet( ATTR_FONT ) ) + { + // tdf#125054 adapt WhichID + pSet->Put(*pItem, EE_CHAR_FONTINFO); + } + if ( const SvxFontItem* pItem = mpPreviewFontSet->GetItemIfSet( ATTR_CJK_FONT ) ) + { + // tdf#125054 adapt WhichID + pSet->Put(*pItem, EE_CHAR_FONTINFO_CJK); + } + if ( const SvxFontItem* pItem = mpPreviewFontSet->GetItemIfSet( ATTR_CTL_FONT ) ) + { + // tdf#125054 adapt WhichID + pSet->Put(*pItem, EE_CHAR_FONTINFO_CTL); + } + } + bool bParaHyphenate = pSet->Get(EE_PARA_HYPHENATE).GetValue(); + mpEngine->SetDefaults( std::move(pSet) ); + mpOldPattern = mpPattern; + mpOldCondSet = mpCondSet; + mpOldPreviewFontSet = mpPreviewFontSet; + + EEControlBits nControl = mpEngine->GetControlWord(); + if (meOrient == SvxCellOrientation::Stacked) + nControl |= EEControlBits::ONECHARPERLINE; + else + nControl &= ~EEControlBits::ONECHARPERLINE; + mpEngine->SetControlWord( nControl ); + + 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; + } + + Color aBackCol = mpPattern->GetItem( ATTR_BACKGROUND, mpCondSet ).GetColor(); + if ( bUseStyleColor && ( aBackCol.IsTransparent() || bCellContrast ) ) + aBackCol = nConfBackColor; + mpEngine->SetBackgroundColor( aBackCol ); +} + +void ScOutputData::DrawEditParam::calcMargins(tools::Long& rTopM, tools::Long& rLeftM, tools::Long& rBottomM, tools::Long& rRightM, double nPPTX, double nPPTY) const +{ + const SvxMarginItem& rMargin = mpPattern->GetItem(ATTR_MARGIN, mpCondSet); + + sal_uInt16 nIndent = 0; + if (meHorJustAttr == SvxCellHorJustify::Left || meHorJustAttr == SvxCellHorJustify::Right) + nIndent = lcl_GetValue<ScIndentItem, sal_uInt16>(*mpPattern, ATTR_INDENT, mpCondSet); + + rLeftM = static_cast<tools::Long>(((rMargin.GetLeftMargin() + nIndent) * nPPTX)); + rTopM = static_cast<tools::Long>((rMargin.GetTopMargin() * nPPTY)); + rRightM = static_cast<tools::Long>((rMargin.GetRightMargin() * nPPTX)); + rBottomM = static_cast<tools::Long>((rMargin.GetBottomMargin() * nPPTY)); + if(meHorJustAttr == SvxCellHorJustify::Right) + { + rLeftM = static_cast<tools::Long>((rMargin.GetLeftMargin() * nPPTX)); + rRightM = static_cast<tools::Long>(((rMargin.GetRightMargin() + nIndent) * nPPTX)); + } +} + +void ScOutputData::DrawEditParam::calcPaperSize( + Size& rPaperSize, const tools::Rectangle& rAlignRect, double nPPTX, double nPPTY) const +{ + tools::Long nTopM, nLeftM, nBottomM, nRightM; + calcMargins(nTopM, nLeftM, nBottomM, nRightM, nPPTX, nPPTY); + + if (isVerticallyOriented()) + { + rPaperSize.setWidth( rAlignRect.GetHeight() - nTopM - nBottomM ); + rPaperSize.setHeight( rAlignRect.GetWidth() - nLeftM - nRightM ); + } + else + { + rPaperSize.setWidth( rAlignRect.GetWidth() - nLeftM - nRightM ); + rPaperSize.setHeight( rAlignRect.GetHeight() - nTopM - nBottomM ); + } + + 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::getEngineSize(ScFieldEditEngine* pEngine, tools::Long& rWidth, tools::Long& rHeight) const +{ + tools::Long nEngineWidth = 0; + if (!mbBreak || meOrient == SvxCellOrientation::Stacked || mbAsianVertical) + nEngineWidth = static_cast<tools::Long>(pEngine->CalcTextWidth()); + + tools::Long nEngineHeight = pEngine->GetTextHeight(); + + if (isVerticallyOriented()) + { + tools::Long nTemp = nEngineWidth; + nEngineWidth = nEngineHeight; + nEngineHeight = nTemp; + } + + if (meOrient == SvxCellOrientation::Stacked) + nEngineWidth = nEngineWidth * 11 / 10; + + rWidth = nEngineWidth; + rHeight = nEngineHeight; +} + +bool ScOutputData::DrawEditParam::hasLineBreak() const +{ + return (mbBreak || (meOrient == SvxCellOrientation::Stacked) || mbAsianVertical); +} + +bool ScOutputData::DrawEditParam::isHyperlinkCell() const +{ + if (maCell.meType != CELLTYPE_FORMULA) + return false; + + return maCell.mpFormula->IsHyperLinkCell(); +} + +bool ScOutputData::DrawEditParam::isVerticallyOriented() const +{ + return (meOrient == SvxCellOrientation::TopBottom || meOrient == SvxCellOrientation::BottomUp); +} + +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; + } + + mpEngine->SetDefaultItem( SvxAdjustItem(eSvxAdjust, EE_PARA_JUST) ); + mpEngine->SetDefaultItem( SvxJustifyMethodItem(meVerJustMethod, EE_PARA_JUST_METHOD) ); + + 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; + else if (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->SetDefaultItem( SvxAdjustItem(eSvxAdjust, EE_PARA_JUST) ); + + if (mbAsianVertical) + { + mpEngine->SetDefaultItem( SvxJustifyMethodItem(meVerJustMethod, EE_PARA_JUST_METHOD) ); + if (meHorJustResult == SvxCellHorJustify::Block) + mpEngine->SetDefaultItem( SvxVerJustifyItem(SvxCellVerJustify::Block, EE_PARA_VER_JUST) ); + } + else + { + mpEngine->SetDefaultItem( SvxJustifyMethodItem(meHorJustMethod, EE_PARA_JUST_METHOD) ); + if (meVerJust == SvxCellVerJustify::Block) + mpEngine->SetDefaultItem( SvxVerJustifyItem(SvxCellVerJustify::Block, EE_PARA_VER_JUST) ); + } + } + + mpEngine->SetVertical(mbAsianVertical); + if (maCell.meType == 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.mpEditText; + if (pData) + const_cast<EditTextObject*>(pData)->SetVertical(mbAsianVertical); + } +} + +bool ScOutputData::DrawEditParam::adjustHorAlignment(ScFieldEditEngine* pEngine) +{ + if (meHorJustResult == SvxCellHorJustify::Right || meHorJustResult == SvxCellHorJustify::Center) + { + SvxAdjust eEditAdjust = (meHorJustResult == SvxCellHorJustify::Center) ? + SvxAdjust::Center : SvxAdjust::Right; + + const bool bPrevUpdateLayout = pEngine->SetUpdateLayout(false); + pEngine->SetDefaultItem( SvxAdjustItem(eEditAdjust, EE_PARA_JUST) ); + pEngine->SetUpdateLayout(bPrevUpdateLayout); + return true; + } + return false; +} + +void ScOutputData::DrawEditParam::adjustForHyperlinkInPDF(Point aURLStart, const OutputDevice* pDev) +{ + // PDF: whole-cell hyperlink from formula? + vcl::PDFExtOutDevData* pPDFData = dynamic_cast<vcl::PDFExtOutDevData* >( pDev->GetExtOutDevData() ); + bool bHasURL = pPDFData && isHyperlinkCell(); + if (!bHasURL) + return; + + tools::Long nURLWidth = static_cast<tools::Long>(mpEngine->CalcTextWidth()); + tools::Long nURLHeight = mpEngine->GetTextHeight(); + if (mbBreak) + { + Size aPaper = mpEngine->GetPaperSize(); + if ( mbAsianVertical ) + nURLHeight = aPaper.Height(); + else + nURLWidth = aPaper.Width(); + } + if (isVerticallyOriented()) + std::swap( nURLWidth, nURLHeight ); + else if (mbAsianVertical) + aURLStart.AdjustX( -nURLWidth ); + + tools::Rectangle aURLRect( aURLStart, Size( nURLWidth, nURLHeight ) ); + lcl_DoHyperlinkResult(pDev, aURLRect, maCell); +} + +// Returns true if the rect is clipped vertically +bool ScOutputData::AdjustAreaParamClipRect(OutputAreaParam& rAreaParam) +{ + if( rAreaParam.maClipRect.Left() < nScrX ) + { + rAreaParam.maClipRect.SetLeft( nScrX ); + rAreaParam.mbLeftClip = true; + } + if( rAreaParam.maClipRect.Right() > nScrX + nScrW ) + { + rAreaParam.maClipRect.SetRight( nScrX + nScrW ); //! minus one? + rAreaParam.mbRightClip = true; + } + + bool bVClip = false; + + if( rAreaParam.maClipRect.Top() < nScrY ) + { + rAreaParam.maClipRect.SetTop( nScrY ); + bVClip = true; + } + if( rAreaParam.maClipRect.Bottom() > nScrY + nScrH ) + { + rAreaParam.maClipRect.SetBottom( nScrY + nScrH ); //! minus one? + bVClip = true; + } + return bVClip; +} + +// 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(); + } + } + + const tools::Rectangle& getRect() const { return maRect; } + +private: + tools::Rectangle maRect; + VclPtr<OutputDevice> mpDev; + bool mbMetaFile; +}; + +// Returns needed width in current units; sets rNeededPixel to needed width in pixels +tools::Long ScOutputData::SetEngineTextAndGetWidth( DrawEditParam& rParam, const OUString& rSetString, + tools::Long& rNeededPixel, tools::Long nAddWidthPixels ) +{ + rParam.mpEngine->SetTextCurrentDefaults( rSetString ); + tools::Long nEngineWidth = static_cast<tools::Long>( rParam.mpEngine->CalcTextWidth() ); + if ( rParam.mbPixelToLogic ) + rNeededPixel = mpRefDevice->LogicToPixel( Size( nEngineWidth, 0 ) ).Width(); + else + rNeededPixel = nEngineWidth; + + rNeededPixel += nAddWidthPixels; + + return nEngineWidth; +} + +void ScOutputData::DrawEditStandard(DrawEditParam& rParam) +{ + OSL_ASSERT(rParam.meOrient == SvxCellOrientation::Standard); + OSL_ASSERT(!rParam.mbAsianVertical); + + Size aRefOne = mpRefDevice->PixelToLogic(Size(1,1)); + + bool bRepeat = (rParam.meHorJustAttr == SvxCellHorJustify::Repeat && !rParam.mbBreak); + bool bShrink = !rParam.mbBreak && !bRepeat && lcl_GetBoolValue(*rParam.mpPattern, ATTR_SHRINKTOFIT, rParam.mpCondSet); + Degree100 nAttrRotate = lcl_GetValue<ScRotateValueItem, Degree100>(*rParam.mpPattern, ATTR_ROTATE_VALUE, rParam.mpCondSet); + + 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 + } + + SvxCellHorJustify eOutHorJust = rParam.meHorJustContext; + + //! mirror margin values for RTL? + //! move margin down to after final GetOutputArea call + tools::Long nTopM, nLeftM, nBottomM, nRightM; + rParam.calcMargins(nTopM, nLeftM, nBottomM, nRightM, mnPPTX, mnPPTY); + + SCCOL nXForPos = rParam.mnX; + if ( nXForPos < nX1 ) + { + nXForPos = nX1; + rParam.mnPosX = rParam.mnInitPosX; + } + SCSIZE nArrYForPos = rParam.mnArrY; + if ( nArrYForPos < 1 ) + { + nArrYForPos = 1; + rParam.mnPosY = nScrY; + } + + OutputAreaParam aAreaParam; + + // Initial page size - large for normal text, cell size for automatic line breaks + + Size aPaperSize( 1000000, 1000000 ); + if (rParam.mbBreak) + { + // call GetOutputArea with nNeeded=0, to get only the cell width + + //! handle nArrY == 0 + GetOutputArea( nXForPos, nArrYForPos, rParam.mnPosX, rParam.mnPosY, rParam.mnCellX, rParam.mnCellY, 0, + *rParam.mpPattern, sal::static_int_cast<sal_uInt16>(eOutHorJust), + rParam.mbCellIsValue, true, false, aAreaParam ); + + //! 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 + + Fraction aFract(1,1); + tools::Rectangle aUtilRect = ScEditUtil( mpDoc, rParam.mnCellX, rParam.mnCellY, nTab, Point(0,0), pFmtDevice, + HMM_PER_TWIPS, HMM_PER_TWIPS, aFract, aFract ).GetEditArea( rParam.mpPattern, false ); + aLogicSize.setWidth( aUtilRect.GetWidth() ); + } + rParam.mpEngine->SetPaperSize(aLogicSize); + } + else + rParam.mpEngine->SetPaperSize(aPaperSize); + + // Fill the EditEngine (cell attributes and text) + + // 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); + + // Read content from cell + + bool bWrapFields = false; + if (!rParam.readCellContent(mpDoc, mbShowNullValues, mbShowFormulas, mbSyntaxMode, mbUseStyleColor, mbForceAutoColor, bWrapFields)) + // Failed to read cell content. Bail out. + return; + + if ( mbSyntaxMode ) + SetEditSyntaxColor(*rParam.mpEngine, rParam.maCell); + else if ( 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 + + tools::Long nEngineWidth, nEngineHeight; + rParam.getEngineSize(rParam.mpEngine, nEngineWidth, nEngineHeight); + + tools::Long nNeededPixel = nEngineWidth; + if (rParam.mbPixelToLogic) + nNeededPixel = mpRefDevice->LogicToPixel(Size(nNeededPixel,0)).Width(); + nNeededPixel += nLeftM + nRightM; + + 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(); + + tools::Long nRepeatSize = 0; + SetEngineTextAndGetWidth( rParam, aCellStr, nRepeatSize, 0 ); + if ( pFmtDevice != mpRefDevice ) + ++nRepeatSize; + if ( nRepeatSize > 0 ) + { + tools::Long nRepeatCount = nAvailable / nRepeatSize; + if ( nRepeatCount > 1 ) + { + OUStringBuffer aRepeated(aCellStr); + for ( tools::Long nRepeat = 1; nRepeat < nRepeatCount; nRepeat++ ) + aRepeated.append(aCellStr); + + SetEngineTextAndGetWidth( rParam, aRepeated.makeStringAndClear(), + nNeededPixel, (nLeftM + nRightM ) ); + + nEngineHeight = rParam.mpEngine->GetTextHeight(); + } + } + } + } + + + if ( rParam.mbCellIsValue && ( aAreaParam.mbLeftClip || aAreaParam.mbRightClip ) ) + { + SetEngineTextAndGetWidth( rParam, "###", nNeededPixel, ( nLeftM + nRightM ) ); + + // No clip marks if "###" doesn't fit (same as in DrawStrings) + } + + if (eOutHorJust != SvxCellHorJustify::Left) + { + aPaperSize.setWidth( nNeededPixel + 1 ); + if (rParam.mbPixelToLogic) + rParam.mpEngine->SetPaperSize(mpRefDevice->PixelToLogic(aPaperSize)); + else + rParam.mpEngine->SetPaperSize(aPaperSize); + } + } + + tools::Long nStartX = aAreaParam.maAlignRect.Left(); + tools::Long nStartY = aAreaParam.maAlignRect.Top(); + tools::Long nCellWidth = aAreaParam.maAlignRect.GetWidth(); + tools::Long nOutWidth = nCellWidth - 1 - nLeftM - nRightM; + tools::Long nOutHeight = aAreaParam.maAlignRect.GetHeight() - nTopM - nBottomM; + + 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 + + nStartX += nLeftM; + } + else + { + if ( eOutHorJust == SvxCellHorJustify::Right ) + nStartX -= nNeededPixel - nCellWidth + nRightM + 1; + else if ( eOutHorJust == SvxCellHorJustify::Center ) + nStartX -= ( nNeededPixel - nCellWidth + nRightM + 1 - nLeftM ) / 2; + else + nStartX += nLeftM; + } + + bool bOutside = (aAreaParam.maClipRect.Right() < nScrX || aAreaParam.maClipRect.Left() >= nScrX + nScrW); + if (bOutside) + return; + + // 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 ); + + if ( nEngineHeight >= aCellSize.Height() + aRefOne.Height() ) + { + const ScMergeAttr* pMerge = &rParam.mpPattern->GetItem(ATTR_MERGE); + bool bMerged = pMerge->GetColMerge() > 1 || pMerge->GetRowMerge() > 1; + + // 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; + + tools::Long nMarkPixel = static_cast<tools::Long>( SC_CLIPMARK_SIZE * mnPPTX ); + if ( aAreaParam.maClipRect.Right() - nMarkPixel > aAreaParam.maClipRect.Left() ) + aAreaParam.maClipRect.AdjustRight( -nMarkPixel ); + + // 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? + + if (rParam.mbPixelToLogic) + aLogicStart.AdjustY(mpRefDevice->PixelToLogic( Size(0, nTopM + + mpRefDevice->LogicToPixel(aCellSize).Height() - + mpRefDevice->LogicToPixel(Size(0,nEngineHeight)).Height() + )).Height() ); + else + aLogicStart.AdjustY(nTopM + aCellSize.Height() - nEngineHeight ); + } + else if (rParam.meVerJust==SvxCellVerJustify::Center) + { + if (rParam.mbPixelToLogic) + aLogicStart.AdjustY(mpRefDevice->PixelToLogic( Size(0, nTopM + ( + mpRefDevice->LogicToPixel(aCellSize).Height() - + mpRefDevice->LogicToPixel(Size(0,nEngineHeight)).Height() ) + / 2)).Height() ); + else + aLogicStart.AdjustY(nTopM + (aCellSize.Height() - nEngineHeight) / 2 ); + } + else // top + { + 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). + + 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); + } + } + + rParam.adjustForHyperlinkInPDF(aURLStart, mpDev); +} + +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; + + 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); + + bAnyClipped = true; + bVertical = true; + const tools::Long nMarkPixel = static_cast<tools::Long>(SC_CLIPMARK_SIZE * mnPPTX); + if (bTop) + { + pClipMarkCell->nClipMark |= ScClipMark::Top; + if (aAreaParam.maClipRect.Top() - nMarkPixel < aAreaParam.maClipRect.Bottom()) + aAreaParam.maClipRect.AdjustTop(+nMarkPixel); + } + else + { + pClipMarkCell->nClipMark |= ScClipMark::Bottom; + if (aAreaParam.maClipRect.Top() - nMarkPixel < aAreaParam.maClipRect.Bottom()) + aAreaParam.maClipRect.AdjustBottom(-nMarkPixel); + } +} + +ClearableClipRegionPtr ScOutputData::Clip( DrawEditParam& rParam, const Size& aCellSize, + OutputAreaParam& aAreaParam, tools::Long nEngineWidth, + bool bWrapFields, bool bTop) +{ + // 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; + + const Size& aRefOne = mpRefDevice->PixelToLogic(Size(1,1)); + if ( nEngineWidth >= aCellSize.Width() + aRefOne.Width() ) + { + const ScMergeAttr* pMerge = &rParam.mpPattern->GetItem(ATTR_MERGE); + const bool bMerged = pMerge->GetColMerge() > 1 || pMerge->GetRowMerge() > 1; + + // 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; + + ShowClipMarks( rParam, nEngineWidth, aCellSize, bMerged, aAreaParam, bTop); + } + + // Clip marks are already handled in GetOutputArea + return ClearableClipRegionPtr(new ClearableClipRegion(rParam.mbPixelToLogic ? + mpRefDevice->PixelToLogic(aAreaParam.maClipRect) + : aAreaParam.maClipRect, bClip, bSimClip, mpDev, bMetaFile)); +} + +void ScOutputData::DrawEditBottomTop(DrawEditParam& rParam) +{ + OSL_ASSERT(rParam.meHorJustAttr != SvxCellHorJustify::Repeat); + + const bool bRepeat = (rParam.meHorJustAttr == SvxCellHorJustify::Repeat && !rParam.mbBreak); + const bool bShrink = !rParam.mbBreak && !bRepeat && lcl_GetBoolValue(*rParam.mpPattern, ATTR_SHRINKTOFIT, rParam.mpCondSet); + + SvxCellHorJustify eOutHorJust = rParam.meHorJustContext; + + //! mirror margin values for RTL? + //! move margin down to after final GetOutputArea call + tools::Long nTopM, nLeftM, nBottomM, nRightM; + rParam.calcMargins(nTopM, nLeftM, nBottomM, nRightM, mnPPTX, mnPPTY); + + SCCOL nXForPos = rParam.mnX; + if ( nXForPos < nX1 ) + { + nXForPos = nX1; + rParam.mnPosX = rParam.mnInitPosX; + } + SCSIZE nArrYForPos = rParam.mnArrY; + if ( nArrYForPos < 1 ) + { + nArrYForPos = 1; + rParam.mnPosY = nScrY; + } + + OutputAreaParam aAreaParam; + + // Initial page size - large for normal text, cell size for automatic line breaks + + Size aPaperSize( 1000000, 1000000 ); + if (rParam.mbBreak) + { + // call GetOutputArea with nNeeded=0, to get only the cell width + + //! handle nArrY == 0 + GetOutputArea( nXForPos, nArrYForPos, rParam.mnPosX, rParam.mnPosY, rParam.mnCellX, rParam.mnCellY, 0, + *rParam.mpPattern, sal::static_int_cast<sal_uInt16>(eOutHorJust), + rParam.mbCellIsValue, true, false, aAreaParam ); + + //! special ScEditUtil handling if formatting for printer + rParam.calcPaperSize(aPaperSize, aAreaParam.maAlignRect, mnPPTX, mnPPTY); + } + if (rParam.mbPixelToLogic) + { + Size aLogicSize = mpRefDevice->PixelToLogic(aPaperSize); + rParam.mpEngine->SetPaperSize(aLogicSize); + } + else + rParam.mpEngine->SetPaperSize(aPaperSize); + + // Fill the EditEngine (cell attributes and text) + + rParam.setPatternToEngine(mbUseStyleColor); + rParam.setAlignmentToEngine(); + + // Read content from cell + + bool bWrapFields = false; + if (!rParam.readCellContent(mpDoc, mbShowNullValues, mbShowFormulas, mbSyntaxMode, mbUseStyleColor, mbForceAutoColor, bWrapFields)) + // Failed to read cell content. Bail out. + return; + + if ( mbSyntaxMode ) + SetEditSyntaxColor( *rParam.mpEngine, rParam.maCell ); + else if ( 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 + + tools::Long nEngineWidth, nEngineHeight; + rParam.getEngineSize(rParam.mpEngine, nEngineWidth, nEngineHeight); + + tools::Long nNeededPixel = nEngineWidth; + if (rParam.mbPixelToLogic) + nNeededPixel = mpRefDevice->LogicToPixel(Size(nNeededPixel,0)).Width(); + nNeededPixel += nLeftM + nRightM; + + 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(); + + tools::Long nRepeatSize = 0; + SetEngineTextAndGetWidth( rParam, aCellStr, nRepeatSize, 0 ); + if ( pFmtDevice != mpRefDevice ) + ++nRepeatSize; + if ( nRepeatSize > 0 ) + { + const tools::Long nRepeatCount = nAvailable / nRepeatSize; + if ( nRepeatCount > 1 ) + { + OUStringBuffer aRepeated(aCellStr); + for ( tools::Long nRepeat = 1; nRepeat < nRepeatCount; nRepeat++ ) + aRepeated.append(aCellStr); + + nEngineWidth = SetEngineTextAndGetWidth( rParam, aRepeated.makeStringAndClear(), + nNeededPixel, (nLeftM + nRightM ) ); + + nEngineHeight = rParam.mpEngine->GetTextHeight(); + } + } + } + } + if ( rParam.mbCellIsValue && ( aAreaParam.mbLeftClip || aAreaParam.mbRightClip ) ) + { + nEngineWidth = SetEngineTextAndGetWidth( rParam, "###", nNeededPixel, ( nLeftM + nRightM ) ); + + // No clip marks if "###" doesn't fit (same as in DrawStrings) + } + } + + tools::Long nStartX = aAreaParam.maAlignRect.Left(); + const tools::Long nStartY = aAreaParam.maAlignRect.Top(); + const tools::Long nCellWidth = aAreaParam.maAlignRect.GetWidth(); + const tools::Long nOutWidth = nCellWidth - 1 - nLeftM - nRightM; + const tools::Long nOutHeight = aAreaParam.maAlignRect.GetHeight() - nTopM - nBottomM; + + 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 + + nStartX += nLeftM; + } + else + { + if ( eOutHorJust == SvxCellHorJustify::Right ) + nStartX -= nNeededPixel - nCellWidth + nRightM + 1; + else if ( eOutHorJust == SvxCellHorJustify::Center ) + nStartX -= ( nNeededPixel - nCellWidth + nRightM + 1 - nLeftM ) / 2; + else + nStartX += nLeftM; + } + + const bool bOutside = (aAreaParam.maClipRect.Right() < nScrX || aAreaParam.maClipRect.Left() >= nScrX + nScrW); + if (bOutside) + return; + + // output area, excluding margins, in logical units + const Size& aCellSize = rParam.mbPixelToLogic + ? mpRefDevice->PixelToLogic( Size( nOutWidth, nOutHeight ) ) + : Size( nOutWidth, nOutHeight ); + + Point aURLStart; + + { + const auto pClipRegion = Clip( rParam, aCellSize, aAreaParam, nEngineWidth, bWrapFields, true ); + + 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); + + tools::Long nGap = 0; + tools::Long nTopOffset = 0; + if (rParam.mbPixelToLogic) + { + nGap = mpRefDevice->LogicToPixel(aCellSize).Height() - mpRefDevice->LogicToPixel(aPSize).Width(); + nGap = mpRefDevice->PixelToLogic(Size(0, nGap)).Height(); + nTopOffset = mpRefDevice->PixelToLogic(Size(0,nTopM)).Height(); + } + else + { + nGap = aCellSize.Height() - aPSize.Width(); + nTopOffset = nTopM; + } + + // 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: + ; + } + } + + rParam.mpEngine->Draw(*mpDev, aLogicStart, 900_deg10); + } + + rParam.adjustForHyperlinkInPDF(aURLStart, mpDev); +} + +void ScOutputData::DrawEditTopBottom(DrawEditParam& rParam) +{ + OSL_ASSERT(rParam.meHorJustAttr != SvxCellHorJustify::Repeat); + + const bool bRepeat = (rParam.meHorJustAttr == SvxCellHorJustify::Repeat && !rParam.mbBreak); + const bool bShrink = !rParam.mbBreak && !bRepeat && lcl_GetBoolValue(*rParam.mpPattern, ATTR_SHRINKTOFIT, rParam.mpCondSet); + + SvxCellHorJustify eOutHorJust = rParam.meHorJustContext; + + //! mirror margin values for RTL? + //! move margin down to after final GetOutputArea call + tools::Long nTopM, nLeftM, nBottomM, nRightM; + rParam.calcMargins(nTopM, nLeftM, nBottomM, nRightM, mnPPTX, mnPPTY); + + SCCOL nXForPos = rParam.mnX; + if ( nXForPos < nX1 ) + { + nXForPos = nX1; + rParam.mnPosX = rParam.mnInitPosX; + } + SCSIZE nArrYForPos = rParam.mnArrY; + if ( nArrYForPos < 1 ) + { + nArrYForPos = 1; + rParam.mnPosY = nScrY; + } + + OutputAreaParam aAreaParam; + + // Initial page size - large for normal text, cell size for automatic line breaks + + Size aPaperSize( 1000000, 1000000 ); + if (rParam.hasLineBreak()) + { + // call GetOutputArea with nNeeded=0, to get only the cell width + + //! handle nArrY == 0 + GetOutputArea( nXForPos, nArrYForPos, rParam.mnPosX, rParam.mnPosY, rParam.mnCellX, rParam.mnCellY, 0, + *rParam.mpPattern, sal::static_int_cast<sal_uInt16>(eOutHorJust), + rParam.mbCellIsValue, true, false, aAreaParam ); + + //! special ScEditUtil handling if formatting for printer + rParam.calcPaperSize(aPaperSize, aAreaParam.maAlignRect, mnPPTX, mnPPTY); + } + if (rParam.mbPixelToLogic) + { + Size aLogicSize = mpRefDevice->PixelToLogic(aPaperSize); + rParam.mpEngine->SetPaperSize(aLogicSize); + } + else + rParam.mpEngine->SetPaperSize(aPaperSize); + + // Fill the EditEngine (cell attributes and text) + + rParam.setPatternToEngine(mbUseStyleColor); + rParam.setAlignmentToEngine(); + + // Read content from cell + + bool bWrapFields = false; + if (!rParam.readCellContent(mpDoc, mbShowNullValues, mbShowFormulas, mbSyntaxMode, mbUseStyleColor, mbForceAutoColor, bWrapFields)) + // Failed to read cell content. Bail out. + return; + + if ( mbSyntaxMode ) + SetEditSyntaxColor( *rParam.mpEngine, rParam.maCell ); + else if ( 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 + + tools::Long nEngineWidth, nEngineHeight; + rParam.getEngineSize(rParam.mpEngine, nEngineWidth, nEngineHeight); + + tools::Long nNeededPixel = nEngineWidth; + if (rParam.mbPixelToLogic) + nNeededPixel = mpRefDevice->LogicToPixel(Size(nNeededPixel,0)).Width(); + nNeededPixel += nLeftM + nRightM; + + 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(); + + tools::Long nRepeatSize = 0; + SetEngineTextAndGetWidth( rParam, aCellStr, nRepeatSize, 0 ); + + if ( pFmtDevice != mpRefDevice ) + ++nRepeatSize; + if ( nRepeatSize > 0 ) + { + const tools::Long nRepeatCount = nAvailable / nRepeatSize; + if ( nRepeatCount > 1 ) + { + OUStringBuffer aRepeated(aCellStr); + for ( tools::Long nRepeat = 1; nRepeat < nRepeatCount; nRepeat++ ) + aRepeated.append(aCellStr); + + nEngineWidth = SetEngineTextAndGetWidth( rParam, aRepeated.makeStringAndClear(), + nNeededPixel, (nLeftM + nRightM ) ); + + nEngineHeight = rParam.mpEngine->GetTextHeight(); + } + } + } + } + if ( rParam.mbCellIsValue && ( aAreaParam.mbLeftClip || aAreaParam.mbRightClip ) ) + { + nEngineWidth = SetEngineTextAndGetWidth( rParam, "###", nNeededPixel, ( nLeftM + nRightM ) ); + + // No clip marks if "###" doesn't fit (same as in DrawStrings) + } + } + + tools::Long nStartX = aAreaParam.maAlignRect.Left(); + const tools::Long nStartY = aAreaParam.maAlignRect.Top(); + const tools::Long nCellWidth = aAreaParam.maAlignRect.GetWidth(); + const tools::Long nOutWidth = nCellWidth - 1 - nLeftM - nRightM; + const tools::Long nOutHeight = aAreaParam.maAlignRect.GetHeight() - nTopM - nBottomM; + + 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 + + nStartX += nLeftM; + if (rParam.meHorJustResult == SvxCellHorJustify::Block) + nStartX += aPaperSize.Height(); + } + else + { + if ( eOutHorJust == SvxCellHorJustify::Right ) + nStartX -= nNeededPixel - nCellWidth + nRightM + 1; + else if ( eOutHorJust == SvxCellHorJustify::Center ) + nStartX -= ( nNeededPixel - nCellWidth + nRightM + 1 - nLeftM ) / 2; + else + nStartX += nLeftM; + } + + const bool bOutside = (aAreaParam.maClipRect.Right() < nScrX || aAreaParam.maClipRect.Left() >= nScrX + nScrW); + if (bOutside) + return; + + // output area, excluding margins, in logical units + const Size& aCellSize = rParam.mbPixelToLogic + ? mpRefDevice->PixelToLogic( Size( nOutWidth, nOutHeight ) ) + : Size( nOutWidth, nOutHeight ); + + Point aURLStart; + + { + const auto pClipRegion = Clip( rParam, aCellSize, aAreaParam, nEngineWidth, bWrapFields, false ); + + 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); + + tools::Long nGap = 0; + tools::Long nTopOffset = 0; // offset by top margin + if (rParam.mbPixelToLogic) + { + nGap = mpRefDevice->LogicToPixel(aPSize).Width() - mpRefDevice->LogicToPixel(aCellSize).Height(); + nGap = mpRefDevice->PixelToLogic(Size(0, nGap)).Height(); + nTopOffset = mpRefDevice->PixelToLogic(Size(0,nTopM)).Height(); + } + else + { + nGap = aPSize.Width() - aCellSize.Height(); + nTopOffset = nTopM; + } + aLogicStart.AdjustY(nTopOffset ); + + 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). + + rParam.mpEngine->Draw(*mpDev, aLogicStart, 2700_deg10); + } + + rParam.adjustForHyperlinkInPDF(aURLStart, mpDev); +} + +void ScOutputData::DrawEditStacked(DrawEditParam& rParam) +{ + OSL_ASSERT(rParam.meHorJustAttr != SvxCellHorJustify::Repeat); + Size aRefOne = mpRefDevice->PixelToLogic(Size(1,1)); + + bool bRepeat = (rParam.meHorJustAttr == SvxCellHorJustify::Repeat && !rParam.mbBreak); + bool bShrink = !rParam.mbBreak && !bRepeat && lcl_GetBoolValue(*rParam.mpPattern, ATTR_SHRINKTOFIT, rParam.mpCondSet); + + rParam.mbAsianVertical = + lcl_GetBoolValue(*rParam.mpPattern, ATTR_VERTICAL_ASIAN, rParam.mpCondSet); + + if ( rParam.mbAsianVertical ) + { + // in asian mode, use EditEngine::SetVertical instead of EEControlBits::ONECHARPERLINE + rParam.meOrient = SvxCellOrientation::Standard; + DrawEditAsianVertical(rParam); + return; + } + + SvxCellHorJustify eOutHorJust = rParam.meHorJustContext; + + //! mirror margin values for RTL? + //! move margin down to after final GetOutputArea call + tools::Long nTopM, nLeftM, nBottomM, nRightM; + rParam.calcMargins(nTopM, nLeftM, nBottomM, nRightM, mnPPTX, mnPPTY); + + SCCOL nXForPos = rParam.mnX; + if ( nXForPos < nX1 ) + { + nXForPos = nX1; + rParam.mnPosX = rParam.mnInitPosX; + } + SCSIZE nArrYForPos = rParam.mnArrY; + if ( nArrYForPos < 1 ) + { + nArrYForPos = 1; + rParam.mnPosY = nScrY; + } + + OutputAreaParam aAreaParam; + + // Initial page size - large for normal text, cell size for automatic line breaks + + Size aPaperSize( 1000000, 1000000 ); + // call GetOutputArea with nNeeded=0, to get only the cell width + + //! handle nArrY == 0 + GetOutputArea( nXForPos, nArrYForPos, rParam.mnPosX, rParam.mnPosY, rParam.mnCellX, rParam.mnCellY, 0, + *rParam.mpPattern, sal::static_int_cast<sal_uInt16>(eOutHorJust), + rParam.mbCellIsValue, true, false, aAreaParam ); + + //! 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 + + Fraction aFract(1,1); + tools::Rectangle aUtilRect = ScEditUtil( mpDoc, rParam.mnCellX, rParam.mnCellY, nTab, Point(0,0), pFmtDevice, + HMM_PER_TWIPS, HMM_PER_TWIPS, aFract, aFract ).GetEditArea( rParam.mpPattern, false ); + aLogicSize.setWidth( aUtilRect.GetWidth() ); + } + rParam.mpEngine->SetPaperSize(aLogicSize); + } + else + rParam.mpEngine->SetPaperSize(aPaperSize); + + // Fill the EditEngine (cell attributes and text) + + rParam.setPatternToEngine(mbUseStyleColor); + rParam.setAlignmentToEngine(); + + // Read content from cell + + bool bWrapFields = false; + if (!rParam.readCellContent(mpDoc, mbShowNullValues, mbShowFormulas, mbSyntaxMode, mbUseStyleColor, mbForceAutoColor, bWrapFields)) + // Failed to read cell content. Bail out. + return; + + if ( mbSyntaxMode ) + SetEditSyntaxColor( *rParam.mpEngine, rParam.maCell ); + else if ( 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 + + tools::Long nEngineWidth, nEngineHeight; + rParam.getEngineSize(rParam.mpEngine, nEngineWidth, nEngineHeight); + + tools::Long nNeededPixel = nEngineWidth; + if (rParam.mbPixelToLogic) + nNeededPixel = mpRefDevice->LogicToPixel(Size(nNeededPixel,0)).Width(); + nNeededPixel += nLeftM + nRightM; + + if (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), + true, false, false, aAreaParam ); + + ShrinkEditEngine( *rParam.mpEngine, aAreaParam.maAlignRect, + nLeftM, nTopM, nRightM, nBottomM, true, + rParam.meOrient, 0_deg100, rParam.mbPixelToLogic, + nEngineWidth, nEngineHeight, nNeededPixel, + aAreaParam.mbLeftClip, aAreaParam.mbRightClip ); + + if ( rParam.mbCellIsValue && ( aAreaParam.mbLeftClip || aAreaParam.mbRightClip ) ) + { + nEngineWidth = SetEngineTextAndGetWidth( rParam, "###", nNeededPixel, ( nLeftM + nRightM ) ); + + // No clip marks if "###" doesn't fit (same as in DrawStrings) + } + + if ( eOutHorJust != SvxCellHorJustify::Left ) + { + aPaperSize.setWidth( nNeededPixel + 1 ); + if (rParam.mbPixelToLogic) + rParam.mpEngine->SetPaperSize(mpRefDevice->PixelToLogic(aPaperSize)); + else + rParam.mpEngine->SetPaperSize(aPaperSize); + } + } + + tools::Long nStartX = aAreaParam.maAlignRect.Left(); + tools::Long nStartY = aAreaParam.maAlignRect.Top(); + tools::Long nCellWidth = aAreaParam.maAlignRect.GetWidth(); + tools::Long nOutWidth = nCellWidth - 1 - nLeftM - nRightM; + tools::Long nOutHeight = aAreaParam.maAlignRect.GetHeight() - nTopM - nBottomM; + + 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 + + nStartX += nLeftM; + } + else + { + if ( eOutHorJust == SvxCellHorJustify::Right ) + nStartX -= nNeededPixel - nCellWidth + nRightM + 1; + else if ( eOutHorJust == SvxCellHorJustify::Center ) + nStartX -= ( nNeededPixel - nCellWidth + nRightM + 1 - nLeftM ) / 2; + else + nStartX += nLeftM; + } + + bool bOutside = (aAreaParam.maClipRect.Right() < nScrX || aAreaParam.maClipRect.Left() >= nScrX + nScrW); + if (bOutside) + return; + + // 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 ); + + if ( nEngineHeight >= aCellSize.Height() + aRefOne.Height() ) + { + const ScMergeAttr* pMerge = &rParam.mpPattern->GetItem(ATTR_MERGE); + bool bMerged = pMerge->GetColMerge() > 1 || pMerge->GetRowMerge() > 1; + + // 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; + + tools::Long nMarkPixel = static_cast<tools::Long>( SC_CLIPMARK_SIZE * mnPPTX ); + if ( aAreaParam.maClipRect.Right() - nMarkPixel > aAreaParam.maClipRect.Left() ) + aAreaParam.maClipRect.AdjustRight( -nMarkPixel ); + } + } + + 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.meVerJust==SvxCellVerJustify::Bottom || + rParam.meVerJust==SvxCellVerJustify::Standard) + { + //! if pRefDevice != pFmtDevice, keep heights in logic units, + //! only converting margin? + + if (rParam.mbPixelToLogic) + aLogicStart.AdjustY(mpRefDevice->PixelToLogic( Size(0, nTopM + + mpRefDevice->LogicToPixel(aCellSize).Height() - + mpRefDevice->LogicToPixel(Size(0,nEngineHeight)).Height() + )).Height() ); + else + aLogicStart.AdjustY(nTopM + aCellSize.Height() - nEngineHeight ); + } + else if (rParam.meVerJust==SvxCellVerJustify::Center) + { + if (rParam.mbPixelToLogic) + aLogicStart.AdjustY(mpRefDevice->PixelToLogic( Size(0, nTopM + ( + mpRefDevice->LogicToPixel(aCellSize).Height() - + mpRefDevice->LogicToPixel(Size(0,nEngineHeight)).Height() ) + / 2)).Height() ); + else + aLogicStart.AdjustY(nTopM + (aCellSize.Height() - nEngineHeight) / 2 ); + } + else // top + { + if (rParam.mbPixelToLogic) + aLogicStart.AdjustY(mpRefDevice->PixelToLogic(Size(0,nTopM)).Height() ); + else + aLogicStart.AdjustY(nTopM ); + } + + aURLStart = aLogicStart; // copy before modifying for orientation + + Size aPaperLogic = rParam.mpEngine->GetPaperSize(); + aPaperLogic.setWidth( nEngineWidth ); + rParam.mpEngine->SetPaperSize(aPaperLogic); + + // 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); + } + } + + rParam.adjustForHyperlinkInPDF(aURLStart, mpDev); +} + +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); + + Size aRefOne = mpRefDevice->PixelToLogic(Size(1,1)); + + bool bHidden = false; + bool bShrink = !rParam.mbBreak && lcl_GetBoolValue(*rParam.mpPattern, ATTR_SHRINKTOFIT, rParam.mpCondSet); + Degree100 nAttrRotate = lcl_GetValue<ScRotateValueItem, Degree100>(*rParam.mpPattern, ATTR_ROTATE_VALUE, rParam.mpCondSet); + + 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; + + if (bHidden) + return; + + SvxCellHorJustify eOutHorJust = rParam.meHorJustContext; + + //! mirror margin values for RTL? + //! move margin down to after final GetOutputArea call + tools::Long nTopM, nLeftM, nBottomM, nRightM; + rParam.calcMargins(nTopM, nLeftM, nBottomM, nRightM, mnPPTX, mnPPTY); + + SCCOL nXForPos = rParam.mnX; + if ( nXForPos < nX1 ) + { + nXForPos = nX1; + rParam.mnPosX = rParam.mnInitPosX; + } + SCSIZE nArrYForPos = rParam.mnArrY; + if ( nArrYForPos < 1 ) + { + nArrYForPos = 1; + rParam.mnPosY = nScrY; + } + + OutputAreaParam aAreaParam; + + // Initial page size - large for normal text, cell size for automatic line breaks + + Size aPaperSize( 1000000, 1000000 ); + // call GetOutputArea with nNeeded=0, to get only the cell width + + //! handle nArrY == 0 + GetOutputArea( nXForPos, nArrYForPos, rParam.mnPosX, rParam.mnPosY, rParam.mnCellX, rParam.mnCellY, 0, + *rParam.mpPattern, sal::static_int_cast<sal_uInt16>(eOutHorJust), + rParam.mbCellIsValue, true, false, aAreaParam ); + + //! 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 + + Fraction aFract(1,1); + tools::Rectangle aUtilRect = ScEditUtil( mpDoc, rParam.mnCellX, rParam.mnCellY, nTab, Point(0,0), pFmtDevice, + HMM_PER_TWIPS, HMM_PER_TWIPS, aFract, aFract ).GetEditArea( rParam.mpPattern, false ); + aLogicSize.setWidth( aUtilRect.GetWidth() ); + } + rParam.mpEngine->SetPaperSize(aLogicSize); + } + else + rParam.mpEngine->SetPaperSize(aPaperSize); + + // Fill the EditEngine (cell attributes and text) + + // default alignment for asian vertical mode is top-right + if ( rParam.meVerJust == SvxCellVerJustify::Standard ) + rParam.meVerJust = SvxCellVerJustify::Top; + + rParam.setPatternToEngine(mbUseStyleColor); + rParam.setAlignmentToEngine(); + + // Read content from cell + + bool bWrapFields = false; + if (!rParam.readCellContent(mpDoc, mbShowNullValues, mbShowFormulas, mbSyntaxMode, mbUseStyleColor, mbForceAutoColor, bWrapFields)) + // Failed to read cell content. Bail out. + return; + + if ( mbSyntaxMode ) + SetEditSyntaxColor( *rParam.mpEngine, rParam.maCell ); + else if ( 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 + + tools::Long nEngineWidth, nEngineHeight; + rParam.getEngineSize(rParam.mpEngine, nEngineWidth, nEngineHeight); + + tools::Long nNeededPixel = nEngineWidth; + if (rParam.mbPixelToLogic) + nNeededPixel = mpRefDevice->LogicToPixel(Size(nNeededPixel,0)).Width(); + nNeededPixel += nLeftM + nRightM; + + // 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 || 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 ( rParam.mbCellIsValue && ( aAreaParam.mbLeftClip || aAreaParam.mbRightClip ) ) + { + nEngineWidth = SetEngineTextAndGetWidth( rParam, "###", nNeededPixel, ( nLeftM + nRightM ) ); + + // No clip marks if "###" doesn't fit (same as in DrawStrings) + } + + if (eOutHorJust != SvxCellHorJustify::Left) + { + aPaperSize.setWidth( nNeededPixel + 1 ); + if (rParam.mbPixelToLogic) + rParam.mpEngine->SetPaperSize(mpRefDevice->PixelToLogic(aPaperSize)); + else + rParam.mpEngine->SetPaperSize(aPaperSize); + } + + tools::Long nStartX = aAreaParam.maAlignRect.Left(); + tools::Long nStartY = aAreaParam.maAlignRect.Top(); + tools::Long nCellWidth = aAreaParam.maAlignRect.GetWidth(); + tools::Long nOutWidth = nCellWidth - 1 - nLeftM - nRightM; + tools::Long nOutHeight = aAreaParam.maAlignRect.GetHeight() - nTopM - nBottomM; + + // text with automatic breaks is aligned only within the + // edit engine's paper size, the output of the whole area + // is always left-aligned + + nStartX += nLeftM; + + bool bOutside = (aAreaParam.maClipRect.Right() < nScrX || aAreaParam.maClipRect.Left() >= nScrX + nScrW); + if (bOutside) + return; + + // 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 ); + + if ( nEngineHeight >= aCellSize.Height() + aRefOne.Height() ) + { + const ScMergeAttr* pMerge = &rParam.mpPattern->GetItem(ATTR_MERGE); + bool bMerged = pMerge->GetColMerge() > 1 || pMerge->GetRowMerge() > 1; + + // 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; + + tools::Long nMarkPixel = static_cast<tools::Long>( SC_CLIPMARK_SIZE * mnPPTX ); + if ( aAreaParam.maClipRect.Right() - nMarkPixel > aAreaParam.maClipRect.Left() ) + aAreaParam.maClipRect.AdjustRight( -nMarkPixel ); + } + } + + 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); + + tools::Long nAvailWidth = aCellSize.Width(); + // space for AutoFilter is already handled in GetOutputArea + + // horizontal alignment + + if (rParam.meHorJustResult==SvxCellHorJustify::Right) + aLogicStart.AdjustX(nAvailWidth - nEngineWidth ); + else if (rParam.meHorJustResult==SvxCellHorJustify::Center) + aLogicStart.AdjustX((nAvailWidth - nEngineWidth) / 2 ); + + // 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()) ); + + rParam.mpEngine->Draw(*mpDev, aLogicStart); + } + + rParam.adjustForHyperlinkInPDF(aURLStart, mpDev); +} + +void ScOutputData::DrawEdit(bool bPixelToLogic) +{ + std::unique_ptr<ScFieldEditEngine> pEngine; + bool bHyphenatorSet = false; + const ScPatternAttr* pOldPattern = nullptr; + const SfxItemSet* pOldCondSet = nullptr; + const SfxItemSet* pOldPreviewFontSet = nullptr; + ScRefCellValue aCell; + + tools::Long nInitPosX = nScrX; + if ( bLayoutRTL ) + { + nInitPosX += nMirrorW - 1; + } + tools::Long nLayoutSign = bLayoutRTL ? -1 : 1; + + //! store nLastContentCol as member! + SCCOL nLastContentCol = mpDoc->MaxCol(); + if ( nX2 < mpDoc->MaxCol() ) + nLastContentCol = sal::static_int_cast<SCCOL>( + nLastContentCol - mpDoc->GetEmptyLinesInBlock( nX2+1, nY1, nTab, mpDoc->MaxCol(), nY2, nTab, DIR_RIGHT ) ); + + 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; + + tools::Long nPosY = nRowPosY; + if ( nArrY == 0 ) + { + nPosY = nScrY; + nY = pRowInfo[1].nRowNo; + SCCOL nOverX; // start of the merged cells + SCROW nOverY; + if (GetMergeOrigin( nX,nY, 1, nOverX,nOverY, true )) + { + nCellX = nOverX; + nCellY = nOverY; + bDoCell = true; + } + } + else if ( 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 ( nTempX > nX && + !IsEmptyCellText( pThisRowInfo, nTempX, nY ) && + !mpDoc->HasAttrib( nTempX,nY,nTab, nX,nY,nTab, HasAttrFlags::Merged | HasAttrFlags::Overlapped ) ) + { + nCellX = nTempX; + bDoCell = true; + } + } + else + { + bDoCell = true; + } + + if ( bDoCell && bEditMode && nCellX == nEditCol && nCellY == nEditRow ) + bDoCell = false; + + const ScPatternAttr* pPattern = nullptr; + const SfxItemSet* pCondSet = nullptr; + if (bDoCell) + { + if ( nCellY == nY && nCellX >= nX1 && nCellX <= nX2 && + !mpDoc->ColHidden(nCellX, nTab) ) + { + ScCellInfo& rCellInfo = pThisRowInfo->cellInfo(nCellX); + pPattern = rCellInfo.pPatternAttr; + pCondSet = rCellInfo.pConditionSet; + aCell = rCellInfo.maCell; + } + else // get from document + { + pPattern = mpDoc->GetPattern( nCellX, nCellY, nTab ); + pCondSet = mpDoc->GetCondResult( nCellX, nCellY, nTab ); + GetVisibleCell( nCellX, nCellY, nTab, aCell ); + } + if (aCell.isEmpty()) + bDoCell = false; + } + if (bDoCell) + { + if ( mpDoc->GetPreviewCellStyle() ) + { + if ( ScStyleSheet* pPreviewStyle = mpDoc->GetPreviewCellStyle( nCellX, nCellY, nTab ) ) + { + pPreviewPattr.reset( new ScPatternAttr(*pPattern) ); + pPreviewPattr->SetStyleSheet(pPreviewStyle); + pPattern = pPreviewPattr.get(); + } + } + SfxItemSet* pPreviewFontSet = mpDoc->GetPreviewFont( nCellX, nCellY, nTab ); + if (!pEngine) + pEngine = CreateOutputEditEngine(); + else + lcl_ClearEdit( *pEngine ); // also calls SetUpdateMode(sal_False) + + // fdo#32530: Check if the first character is RTL. + OUString aStr = mpDoc->GetString(nCellX, nCellY, nTab); + + DrawEditParam aParam(pPattern, pCondSet, lcl_SafeIsValue(aCell)); + const bool bNumberFormatIsText = lcl_isNumberFormatText( mpDoc, nCellX, nCellY, nTab ); + aParam.meHorJustContext = getAlignmentFromContext( aParam.meHorJustAttr, + aParam.mbCellIsValue, aStr, *pPattern, pCondSet, mpDoc, nTab, bNumberFormatIsText); + aParam.meHorJustResult = (aParam.meHorJustAttr == SvxCellHorJustify::Block) ? + SvxCellHorJustify::Block : aParam.meHorJustContext; + aParam.mbPixelToLogic = bPixelToLogic; + aParam.mbHyphenatorSet = bHyphenatorSet; + aParam.mpEngine = pEngine.get(); + aParam.maCell = aCell; + aParam.mnArrY = nArrY; + aParam.mnX = nX; + aParam.mnCellX = nCellX; + aParam.mnCellY = nCellY; + aParam.mnPosX = nPosX; + aParam.mnPosY = nPosY; + aParam.mnInitPosX = nInitPosX; + aParam.mpPreviewFontSet = pPreviewFontSet; + aParam.mpOldPattern = pOldPattern; + aParam.mpOldCondSet = pOldCondSet; + aParam.mpOldPreviewFontSet = pOldPreviewFontSet; + aParam.mpThisRowInfo = pThisRowInfo; + if (mpSpellCheckCxt) + aParam.mpMisspellRanges = mpSpellCheckCxt->getMisspellRanges(nCellX, nCellY); + + 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); + } + + // Retrieve parameters for next iteration. + pOldPattern = aParam.mpOldPattern; + pOldCondSet = aParam.mpOldCondSet; + pOldPreviewFontSet = aParam.mpOldPreviewFontSet; + bHyphenatorSet = aParam.mbHyphenatorSet; + } + } + nPosX += pRowInfo[0].basicCellInfo(nX).nWidth * nLayoutSign; + } + } + nRowPosY += pRowInfo[nArrY].nHeight; + } + + pEngine.reset(); + + if (mrTabInfo.maArray.HasCellRotation()) + { + DrawRotated(bPixelToLogic); //! call from outside ? + } +} + +void ScOutputData::DrawRotated(bool bPixelToLogic) +{ + //! store nRotMax + SCCOL nRotMax = nX2; + for (SCSIZE nRotY=0; nRotY<nArrCount; nRotY++) + if (pRowInfo[nRotY].nRotMaxCol != SC_ROTMAX_NONE && pRowInfo[nRotY].nRotMaxCol > nRotMax) + nRotMax = pRowInfo[nRotY].nRotMaxCol; + + ScModule* pScMod = SC_MOD(); + Color nConfBackColor = pScMod->GetColorConfig().GetColorValue(svtools::DOCCOLOR).nColor; + bool bCellContrast = mbUseStyleColor && + Application::GetSettings().GetStyleSettings().GetHighContrastMode(); + + std::unique_ptr<ScFieldEditEngine> pEngine; + bool bHyphenatorSet = false; + const ScPatternAttr* pPattern; + const SfxItemSet* pCondSet; + const ScPatternAttr* pOldPattern = nullptr; + const SfxItemSet* pOldCondSet = nullptr; + ScRefCellValue aCell; + + tools::Long nInitPosX = nScrX; + if ( bLayoutRTL ) + { + nInitPosX += nMirrorW - 1; + } + tools::Long nLayoutSign = bLayoutRTL ? -1 : 1; + + 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) + { + if (!pEngine) + pEngine = CreateOutputEditEngine(); + else + lcl_ClearEdit( *pEngine ); // also calls SetUpdateMode(sal_False) + + tools::Long nPosY = nRowPosY; + + //! rest from merged cells further up do not work! + + bool bFromDoc = false; + pPattern = pInfo->pPatternAttr; + pCondSet = pInfo->pConditionSet; + if (!pPattern) + { + pPattern = mpDoc->GetPattern( nX, nY, nTab ); + bFromDoc = true; + } + aCell = pInfo->maCell; + if (bFromDoc) + pCondSet = mpDoc->GetCondResult( nX, nY, nTab ); + + if (aCell.isEmpty() && nX>nX2) + GetVisibleCell( nX, nY, nTab, aCell ); + + if (aCell.isEmpty() || IsEmptyCellText(pThisRowInfo, nX, nY)) + bHidden = true; // nRotateDir is also set without a cell + + tools::Long nCellWidth = static_cast<tools::Long>(pRowInfo[0].basicCellInfo(nX).nWidth); + + SvxCellHorJustify eHorJust = + pPattern->GetItem(ATTR_HOR_JUSTIFY, pCondSet).GetValue(); + bool bBreak = ( eHorJust == SvxCellHorJustify::Block ) || + pPattern->GetItem(ATTR_LINEBREAK, pCondSet).GetValue(); + bool bRepeat = ( eHorJust == SvxCellHorJustify::Repeat && !bBreak ); + bool bShrink = !bBreak && !bRepeat && + pPattern->GetItem( ATTR_SHRINKTOFIT, pCondSet ).GetValue(); + SvxCellOrientation eOrient = pPattern->GetCellOrientation( pCondSet ); + + const ScMergeAttr* pMerge = &pPattern->GetItem(ATTR_MERGE); + bool bMerged = pMerge->GetColMerge() > 1 || pMerge->GetRowMerge() > 1; + + tools::Long nStartX = nPosX; + tools::Long nStartY = nPosY; + if (nX<nX1) + { + if ((bBreak || eOrient!=SvxCellOrientation::Standard) && !bMerged) + bHidden = true; + else + { + nStartX = nInitPosX; + SCCOL nCol = nX1; + while (nCol > nX) + { + --nCol; + nStartX -= nLayoutSign * static_cast<tools::Long>(pRowInfo[0].basicCellInfo(nCol).nWidth); + } + } + } + tools::Long nCellStartX = nStartX; + + // omit substitute representation of small text + + if (!bHidden) + { + tools::Long nOutWidth = nCellWidth - 1; + tools::Long nOutHeight = nCellHeight; + + if ( bMerged ) + { + SCCOL nCountX = pMerge->GetColMerge(); + for (SCCOL i=1; i<nCountX; i++) + nOutWidth += mpDoc->GetColWidth(nX+i,nTab) * mnPPTX; + SCROW nCountY = pMerge->GetRowMerge(); + nOutHeight += mpDoc->GetScaledRowHeight( nY+1, nY+nCountY-1, nTab, mnPPTY); + } + + SvxCellVerJustify eVerJust = + pPattern->GetItem(ATTR_VER_JUSTIFY, pCondSet).GetValue(); + + // syntax mode is ignored here... + + // StringDiffer doesn't look at hyphenate, language items + if ( pPattern != pOldPattern || pCondSet != pOldCondSet ) + { + auto pSet = std::make_unique<SfxItemSet>( pEngine->GetEmptyItemSet() ); + pPattern->FillEditItemSet( pSet.get(), pCondSet ); + + // adjustment for EditEngine + SvxAdjust eSvxAdjust = SvxAdjust::Left; + if (eOrient==SvxCellOrientation::Stacked) + eSvxAdjust = SvxAdjust::Center; + // adjustment for bBreak is omitted here + pSet->Put( SvxAdjustItem( eSvxAdjust, EE_PARA_JUST ) ); + + bool bParaHyphenate = pSet->Get(EE_PARA_HYPHENATE).GetValue(); + pEngine->SetDefaults( std::move(pSet) ); + pOldPattern = pPattern; + pOldCondSet = pCondSet; + + EEControlBits nControl = pEngine->GetControlWord(); + if (eOrient==SvxCellOrientation::Stacked) + nControl |= EEControlBits::ONECHARPERLINE; + else + nControl &= ~EEControlBits::ONECHARPERLINE; + pEngine->SetControlWord( nControl ); + + if ( !bHyphenatorSet && bParaHyphenate ) + { + // set hyphenator the first time it is needed + css::uno::Reference<css::linguistic2::XHyphenator> xXHyphenator( LinguMgr::GetHyphenator() ); + pEngine->SetHyphenator( xXHyphenator ); + bHyphenatorSet = true; + } + + Color aBackCol = + pPattern->GetItem( ATTR_BACKGROUND, pCondSet ).GetColor(); + if ( mbUseStyleColor && ( aBackCol.IsTransparent() || bCellContrast ) ) + aBackCol = nConfBackColor; + pEngine->SetBackgroundColor( aBackCol ); + } + + // margins + + //! change position and paper size to EditUtil !!! + + const SvxMarginItem* pMargin = + &pPattern->GetItem(ATTR_MARGIN, pCondSet); + sal_uInt16 nIndent = 0; + if ( eHorJust == SvxCellHorJustify::Left ) + nIndent = pPattern->GetItem(ATTR_INDENT, pCondSet).GetValue(); + + tools::Long nTotalHeight = nOutHeight; // without subtracting the margin + if ( bPixelToLogic ) + nTotalHeight = mpRefDevice->PixelToLogic(Size(0,nTotalHeight)).Height(); + + tools::Long nLeftM = static_cast<tools::Long>( (pMargin->GetLeftMargin() + nIndent) * mnPPTX ); + tools::Long nTopM = static_cast<tools::Long>( pMargin->GetTopMargin() * mnPPTY ); + tools::Long nRightM = static_cast<tools::Long>( pMargin->GetRightMargin() * mnPPTX ); + tools::Long nBottomM = static_cast<tools::Long>( pMargin->GetBottomMargin() * mnPPTY ); + nStartX += nLeftM; + nStartY += nTopM; + nOutWidth -= nLeftM + nRightM; + nOutHeight -= nTopM + nBottomM; + + // 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(); + + if ( nAttrRotate == 18000_deg100 ) + eRotMode = SVX_ROTATE_MODE_STANDARD; // no overflow + + if ( bLayoutRTL ) + nAttrRotate = -nAttrRotate; + + double nRealOrient = toRadians(nAttrRotate); // 1/100 degree + nCos = cos( nRealOrient ); + nSin = sin( nRealOrient ); + } + } + + Size aPaperSize( 1000000, 1000000 ); + if (eOrient==SvxCellOrientation::Stacked) + aPaperSize.setWidth( nOutWidth ); // to center + else if (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)) ); + } + else if (eOrient == SvxCellOrientation::Standard) + aPaperSize.setWidth( nOutWidth ); + else + aPaperSize.setWidth( nOutHeight - 1 ); + } + if (bPixelToLogic) + pEngine->SetPaperSize(mpRefDevice->PixelToLogic(aPaperSize)); + else + pEngine->SetPaperSize(aPaperSize); // scale is always 1 + + // read data from cell + + if (aCell.meType == CELLTYPE_EDIT) + { + if (aCell.mpEditText) + pEngine->SetTextCurrentDefaults(*aCell.mpEditText); + else + { + OSL_FAIL("pData == 0"); + } + } + else + { + sal_uInt32 nFormat = pPattern->GetNumberFormat( + mpDoc->GetFormatTable(), pCondSet ); + const Color* pColor; + OUString aString = ScCellFormat::GetString( aCell, + nFormat, &pColor, + *mpDoc->GetFormatTable(), + *mpDoc, + mbShowNullValues, + mbShowFormulas); + + pEngine->SetTextCurrentDefaults(aString); + if ( pColor && !mbSyntaxMode && !( mbUseStyleColor && mbForceAutoColor ) ) + lcl_SetEditColor( *pEngine, *pColor ); + } + + if ( mbSyntaxMode ) + { + SetEditSyntaxColor(*pEngine, aCell); + } + else if ( mbUseStyleColor && mbForceAutoColor ) + lcl_SetEditColor( *pEngine, COL_AUTO ); //! or have a flag at EditEngine + + pEngine->SetUpdateLayout( true ); // after SetText, before CalcTextWidth/GetTextHeight + + tools::Long nEngineWidth = static_cast<tools::Long>(pEngine->CalcTextWidth()); + tools::Long nEngineHeight = pEngine->GetTextHeight(); + + if (nAttrRotate && bBreak) + { + double nAbsCos = fabs( nCos ); + double nAbsSin = fabs( nSin ); + + // 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) + pEngine->SetPaperSize(mpRefDevice->PixelToLogic(aPaperSize)); + else + pEngine->SetPaperSize(aPaperSize); // Scale is always 1 + //pEngine->QuickFormatDoc( sal_True ); + + nEngineWidth = static_cast<tools::Long>(pEngine->CalcTextWidth()); + nEngineHeight = pEngine->GetTextHeight(); + } + } + } + + tools::Long nRealWidth = nEngineWidth; + tools::Long nRealHeight = nEngineHeight; + + // when rotated, adjust size + if (nAttrRotate) + { + double nAbsCos = fabs( nCos ); + double nAbsSin = fabs( nSin ); + + if ( eRotMode == SVX_ROTATE_MODE_STANDARD ) + nEngineWidth = static_cast<tools::Long>( nRealWidth * nAbsCos + + nRealHeight * nAbsSin ); + else + nEngineWidth = static_cast<tools::Long>( nRealHeight / nAbsSin ); + //! limit !!! + + nEngineHeight = static_cast<tools::Long>( nRealHeight * nAbsCos + + nRealWidth * nAbsSin ); + } + + if (!nAttrRotate) // only rotated text here + bHidden = true; //! check first !!! + + //! omit which doesn't stick out + + if (!bHidden) + { + Size aClipSize( nScrX+nScrW-nStartX, nScrY+nScrH-nStartY ); + + // go on writing + + Size aCellSize; + if (bPixelToLogic) + aCellSize = mpRefDevice->PixelToLogic( Size( nOutWidth, nOutHeight ) ); + else + aCellSize = Size( nOutWidth, nOutHeight ); // scale is one + + tools::Long nGridWidth = nEngineWidth; + bool bNegative = false; + if ( eRotMode != SVX_ROTATE_MODE_STANDARD ) + { + nGridWidth = aCellSize.Width() + + std::abs(static_cast<tools::Long>( aCellSize.Height() * nCos / nSin )); + bNegative = ( pInfo->nRotateDir == ScRotateDir::Left ); + if ( bLayoutRTL ) + bNegative = !bNegative; + } + + // use GetOutputArea to hide the grid + // (clip region is done manually below) + OutputAreaParam aAreaParam; + + SCCOL nCellX = nX; + SCROW nCellY = nY; + SvxCellHorJustify eOutHorJust = eHorJust; + if ( eRotMode != SVX_ROTATE_MODE_STANDARD ) + eOutHorJust = bNegative ? SvxCellHorJustify::Right : SvxCellHorJustify::Left; + tools::Long nNeededWidth = nGridWidth; // in pixel for GetOutputArea + if ( bPixelToLogic ) + nNeededWidth = mpRefDevice->LogicToPixel(Size(nNeededWidth,0)).Width(); + + GetOutputArea( nX, nArrY, nCellStartX, nPosY, nCellX, nCellY, nNeededWidth, + *pPattern, sal::static_int_cast<sal_uInt16>(eOutHorJust), + false, false, true, aAreaParam ); + + if ( bShrink ) + { + tools::Long nPixelWidth = bPixelToLogic ? + mpRefDevice->LogicToPixel(Size(nEngineWidth,0)).Width() : nEngineWidth; + tools::Long nNeededPixel = nPixelWidth + nLeftM + nRightM; + + aAreaParam.mbLeftClip = aAreaParam.mbRightClip = true; + + // always do height + ShrinkEditEngine( *pEngine, aAreaParam.maAlignRect, nLeftM, nTopM, nRightM, nBottomM, + false, eOrient, nAttrRotate, bPixelToLogic, + nEngineWidth, nEngineHeight, nNeededPixel, aAreaParam.mbLeftClip, aAreaParam.mbRightClip ); + + if ( eRotMode == SVX_ROTATE_MODE_STANDARD ) + { + // do width only if rotating within the cell (standard mode) + ShrinkEditEngine( *pEngine, 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>(pEngine->CalcTextWidth()); + nRealHeight = pEngine->GetTextHeight(); + + if ( eRotMode != SVX_ROTATE_MODE_STANDARD ) + nEngineWidth = static_cast<tools::Long>( nRealHeight / fabs( nSin ) ); + } + + tools::Long nClipStartX = nStartX; + if (nX<nX1) + { + //! clipping is not needed when on the left side of the window + + if (nStartX<nScrX) + { + tools::Long nDif = nScrX - nStartX; + nClipStartX = nScrX; + aClipSize.AdjustWidth( -nDif ); + } + } + + tools::Long nClipStartY = nStartY; + if (nArrY==0 && nClipStartY < nRowPosY ) + { + tools::Long nDif = nRowPosY - nClipStartY; + nClipStartY = nRowPosY; + aClipSize.AdjustHeight( -nDif ); + } + + if ( nAttrRotate /* && eRotMode != SVX_ROTATE_MODE_STANDARD */ ) + { + // only clip rotated output text at the page border + nClipStartX = nScrX; + aClipSize.setWidth( nScrW ); + } + + if (bPixelToLogic) + aAreaParam.maClipRect = mpRefDevice->PixelToLogic( tools::Rectangle( + Point(nClipStartX,nClipStartY), aClipSize ) ); + else + aAreaParam.maClipRect = tools::Rectangle(Point(nClipStartX, nClipStartY), + aClipSize ); // Scale = 1 + + if (bMetaFile) + { + mpDev->Push(); + mpDev->IntersectClipRegion( aAreaParam.maClipRect ); + } + else + mpDev->SetClipRegion( vcl::Region( aAreaParam.maClipRect ) ); + + Point aLogicStart; + if (bPixelToLogic) + aLogicStart = mpRefDevice->PixelToLogic( Point(nStartX,nStartY) ); + else + aLogicStart = Point(nStartX, nStartY); + if ( eOrient!=SvxCellOrientation::Standard || !bBreak ) + { + tools::Long nAvailWidth = aCellSize.Width(); + if (eType==OUTTYPE_WINDOW && + eOrient!=SvxCellOrientation::Stacked && + pInfo->bAutoFilter) + { + // filter drop-down width depends on row height + double fZoom = mpRefDevice ? static_cast<double>(mpRefDevice->GetMapMode().GetScaleY()) : 1.0; + fZoom = fZoom > 1.0 ? fZoom : 1.0; + if (bPixelToLogic) + nAvailWidth -= mpRefDevice->PixelToLogic(Size(0,fZoom * DROPDOWN_BITMAP_SIZE)).Height(); + else + nAvailWidth -= fZoom * DROPDOWN_BITMAP_SIZE; + tools::Long nComp = nEngineWidth; + if (nAvailWidth<nComp) nAvailWidth=nComp; + } + + // horizontal orientation + + if (eOrient==SvxCellOrientation::Standard && !nAttrRotate) + { + if (eHorJust==SvxCellHorJustify::Right || + eHorJust==SvxCellHorJustify::Center) + { + pEngine->SetUpdateLayout( false ); + + SvxAdjust eSvxAdjust = + (eHorJust==SvxCellHorJustify::Right) ? + SvxAdjust::Right : SvxAdjust::Center; + pEngine->SetDefaultItem( + SvxAdjustItem( eSvxAdjust, EE_PARA_JUST ) ); + + aPaperSize.setWidth( nOutWidth ); + if (bPixelToLogic) + pEngine->SetPaperSize(mpRefDevice->PixelToLogic(aPaperSize)); + else + pEngine->SetPaperSize(aPaperSize); + + pEngine->SetUpdateLayout( true ); + } + } + else + { + // rotated text is centered by default + if (eHorJust==SvxCellHorJustify::Right) + aLogicStart.AdjustX(nAvailWidth - nEngineWidth ); + else if (eHorJust==SvxCellHorJustify::Center || + eHorJust==SvxCellHorJustify::Standard) + aLogicStart.AdjustX((nAvailWidth - nEngineWidth) / 2 ); + } + } + + if ( bLayoutRTL ) + { + if (bPixelToLogic) + aLogicStart.AdjustX( -(mpRefDevice->PixelToLogic( + Size( nCellWidth, 0 ) ).Width()) ); + else + aLogicStart.AdjustX( -nCellWidth ); + } + + if ( eOrient==SvxCellOrientation::Standard || + eOrient==SvxCellOrientation::Stacked || !bBreak ) + { + if (eVerJust==SvxCellVerJustify::Bottom || + eVerJust==SvxCellVerJustify::Standard) + { + if (bPixelToLogic) + aLogicStart.AdjustY(mpRefDevice->PixelToLogic( Size(0, + mpRefDevice->LogicToPixel(aCellSize).Height() - + mpRefDevice->LogicToPixel(Size(0,nEngineHeight)).Height() + )).Height() ); + else + aLogicStart.AdjustY(aCellSize.Height() - nEngineHeight ); + } + + else if (eVerJust==SvxCellVerJustify::Center) + { + if (bPixelToLogic) + aLogicStart.AdjustY(mpRefDevice->PixelToLogic( Size(0,( + mpRefDevice->LogicToPixel(aCellSize).Height() - + mpRefDevice->LogicToPixel(Size(0,nEngineHeight)).Height()) + / 2)).Height() ); + else + aLogicStart.AdjustY((aCellSize.Height() - nEngineHeight) / 2 ); + } + } + + // 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); + + double nAddX = 0.0; + double nAddY = 0.0; + if ( nCos > 0.0 && eRotMode != SVX_ROTATE_MODE_STANDARD ) + { + //! limit !!! + double nH = nRealHeight * nCos; + nAddX += nH * ( nCos / fabs(nSin) ); + } + if ( nCos < 0.0 && eRotMode == SVX_ROTATE_MODE_STANDARD ) + nAddX -= nRealWidth * nCos; + if ( nSin < 0.0 ) + nAddX -= nRealHeight * nSin; + if ( nSin > 0.0 ) + nAddY += nRealWidth * nSin; + if ( nCos < 0.0 ) + nAddY -= nRealHeight * nCos; + + if ( eRotMode != SVX_ROTATE_MODE_STANDARD ) + { + //! limit !!! + double nSkew = nTotalHeight * nCos / fabs(nSin); + if ( eRotMode == SVX_ROTATE_MODE_CENTER ) + nAddX -= nSkew * 0.5; + if ( ( eRotMode == SVX_ROTATE_MODE_TOP && nSin > 0.0 ) || + ( eRotMode == SVX_ROTATE_MODE_BOTTOM && nSin < 0.0 ) ) + nAddX -= nSkew; + + tools::Long nUp = 0; + if ( eVerJust == SvxCellVerJustify::Center ) + nUp = ( aCellSize.Height() - nEngineHeight ) / 2; + else if ( eVerJust == SvxCellVerJustify::Top ) + { + if ( nSin > 0.0 ) + nUp = aCellSize.Height() - nEngineHeight; + } + else // BOTTOM / STANDARD + { + if ( nSin < 0.0 ) + nUp = aCellSize.Height() - nEngineHeight; + } + if ( nUp ) + nAddX += ( nUp * nCos / fabs(nSin) ); + } + + aLogicStart.AdjustX(static_cast<tools::Long>(nAddX) ); + aLogicStart.AdjustY(static_cast<tools::Long>(nAddY) ); + } + + // bSimClip is not used here (because nOriVal is set) + + pEngine->Draw(*mpDev, aLogicStart, nOriVal); + + if (bMetaFile) + mpDev->Pop(); + else + mpDev->SetClipRegion(); + } + } + } + } + nPosX += pRowInfo[0].basicCellInfo(nX).nWidth * nLayoutSign; + } + } + nRowPosY += pRowInfo[nArrY].nHeight; + } +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ |