diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-15 05:54:39 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-15 05:54:39 +0000 |
commit | 267c6f2ac71f92999e969232431ba04678e7437e (patch) | |
tree | 358c9467650e1d0a1d7227a21dac2e3d08b622b2 /svx/source/svdraw/svdotextdecomposition.cxx | |
parent | Initial commit. (diff) | |
download | libreoffice-267c6f2ac71f92999e969232431ba04678e7437e.tar.xz libreoffice-267c6f2ac71f92999e969232431ba04678e7437e.zip |
Adding upstream version 4:24.2.0.upstream/4%24.2.0
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'svx/source/svdraw/svdotextdecomposition.cxx')
-rw-r--r-- | svx/source/svdraw/svdotextdecomposition.cxx | 1838 |
1 files changed, 1838 insertions, 0 deletions
diff --git a/svx/source/svdraw/svdotextdecomposition.cxx b/svx/source/svdraw/svdotextdecomposition.cxx new file mode 100644 index 0000000000..1bad74cebb --- /dev/null +++ b/svx/source/svdraw/svdotextdecomposition.cxx @@ -0,0 +1,1838 @@ +/* -*- 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 <svx/compatflags.hxx> +#include <svx/svdetc.hxx> +#include <svx/svdoutl.hxx> +#include <svx/svdpage.hxx> +#include <svx/svdotext.hxx> +#include <svx/svdmodel.hxx> +#include <svx/sdasitm.hxx> +#include <textchain.hxx> +#include <textchainflow.hxx> +#include <svx/sdtacitm.hxx> +#include <svx/sdtayitm.hxx> +#include <svx/sdtaiitm.hxx> +#include <svx/sdtaaitm.hxx> +#include <svx/xfillit0.hxx> +#include <svx/xbtmpit.hxx> +#include <basegfx/vector/b2dvector.hxx> +#include <sdr/primitive2d/sdrtextprimitive2d.hxx> +#include <drawinglayer/primitive2d/drawinglayer_primitivetypes2d.hxx> +#include <drawinglayer/primitive2d/textprimitive2d.hxx> +#include <drawinglayer/primitive2d/textdecoratedprimitive2d.hxx> +#include <basegfx/range/b2drange.hxx> +#include <editeng/eeitem.hxx> +#include <editeng/editstat.hxx> +#include <editeng/smallcaps.hxx> +#include <tools/helpers.hxx> +#include <svl/itemset.hxx> +#include <drawinglayer/animation/animationtiming.hxx> +#include <basegfx/color/bcolor.hxx> +#include <vcl/svapp.hxx> +#include <editeng/escapementitem.hxx> +#include <editeng/svxenum.hxx> +#include <editeng/flditem.hxx> +#include <editeng/adjustitem.hxx> +#include <drawinglayer/primitive2d/texthierarchyprimitive2d.hxx> +#include <drawinglayer/primitive2d/wrongspellprimitive2d.hxx> +#include <drawinglayer/primitive2d/graphicprimitive2d.hxx> +#include <drawinglayer/primitive2d/textlayoutdevice.hxx> +#include <drawinglayer/primitive2d/unifiedtransparenceprimitive2d.hxx> +#include <svx/unoapi.hxx> +#include <drawinglayer/geometry/viewinformation2d.hxx> +#include <editeng/outlobj.hxx> +#include <basegfx/matrix/b2dhommatrixtools.hxx> +#include <sal/log.hxx> +#include <osl/diagnose.h> + +using namespace com::sun::star; + +// helpers + +namespace +{ + rtl::Reference<drawinglayer::primitive2d::BasePrimitive2D> buildTextPortionPrimitive(const DrawPortionInfo& rInfo, const OUString& rText, + const drawinglayer::attribute::FontAttribute& rFontAttribute, + const std::vector<double>& rDXArray, + const basegfx::B2DHomMatrix& rNewTransform); + + class impTextBreakupHandler + { + private: + drawinglayer::primitive2d::Primitive2DContainer maTextPortionPrimitives; + drawinglayer::primitive2d::Primitive2DContainer maLinePrimitives; + drawinglayer::primitive2d::Primitive2DContainer maParagraphPrimitives; + + SdrOutliner& mrOutliner; + basegfx::B2DHomMatrix maNewTransformA; + basegfx::B2DHomMatrix maNewTransformB; + + // the visible area for contour text decomposition + basegfx::B2DVector maScale; + + // ClipRange for BlockText decomposition; only text portions completely + // inside are to be accepted, so this is different from geometric clipping + // (which would allow e.g. upper parts of portions to remain). Only used for + // BlockText (see there) + basegfx::B2DRange maClipRange; + + DECL_LINK(decomposeContourTextPrimitive, DrawPortionInfo*, void); + DECL_LINK(decomposeBlockTextPrimitive, DrawPortionInfo*, void); + DECL_LINK(decomposeStretchTextPrimitive, DrawPortionInfo*, void); + + DECL_LINK(decomposeContourBulletPrimitive, DrawBulletInfo*, void); + DECL_LINK(decomposeBlockBulletPrimitive, DrawBulletInfo*, void); + DECL_LINK(decomposeStretchBulletPrimitive, DrawBulletInfo*, void); + + static rtl::Reference<drawinglayer::primitive2d::BasePrimitive2D> impCheckFieldPrimitive(drawinglayer::primitive2d::BasePrimitive2D* pPrimitive, const DrawPortionInfo& rInfo); + void impFlushTextPortionPrimitivesToLinePrimitives(); + void impFlushLinePrimitivesToParagraphPrimitives(sal_Int32 nPara); + void impHandleDrawPortionInfo(const DrawPortionInfo& rInfo); + void impHandleDrawBulletInfo(const DrawBulletInfo& rInfo); + + public: + explicit impTextBreakupHandler(SdrOutliner& rOutliner) + : mrOutliner(rOutliner) + { + } + + void decomposeContourTextPrimitive(const basegfx::B2DHomMatrix& rNewTransformA, const basegfx::B2DHomMatrix& rNewTransformB, const basegfx::B2DVector& rScale) + { + maScale = rScale; + maNewTransformA = rNewTransformA; + maNewTransformB = rNewTransformB; + mrOutliner.SetDrawPortionHdl(LINK(this, impTextBreakupHandler, decomposeContourTextPrimitive)); + mrOutliner.SetDrawBulletHdl(LINK(this, impTextBreakupHandler, decomposeContourBulletPrimitive)); + mrOutliner.StripPortions(); + mrOutliner.SetDrawPortionHdl(Link<DrawPortionInfo*,void>()); + mrOutliner.SetDrawBulletHdl(Link<DrawBulletInfo*,void>()); + } + + void decomposeBlockTextPrimitive( + const basegfx::B2DHomMatrix& rNewTransformA, + const basegfx::B2DHomMatrix& rNewTransformB, + const basegfx::B2DRange& rClipRange) + { + maNewTransformA = rNewTransformA; + maNewTransformB = rNewTransformB; + maClipRange = rClipRange; + mrOutliner.SetDrawPortionHdl(LINK(this, impTextBreakupHandler, decomposeBlockTextPrimitive)); + mrOutliner.SetDrawBulletHdl(LINK(this, impTextBreakupHandler, decomposeBlockBulletPrimitive)); + mrOutliner.StripPortions(); + mrOutliner.SetDrawPortionHdl(Link<DrawPortionInfo*,void>()); + mrOutliner.SetDrawBulletHdl(Link<DrawBulletInfo*,void>()); + } + + void decomposeStretchTextPrimitive(const basegfx::B2DHomMatrix& rNewTransformA, const basegfx::B2DHomMatrix& rNewTransformB) + { + maNewTransformA = rNewTransformA; + maNewTransformB = rNewTransformB; + mrOutliner.SetDrawPortionHdl(LINK(this, impTextBreakupHandler, decomposeStretchTextPrimitive)); + mrOutliner.SetDrawBulletHdl(LINK(this, impTextBreakupHandler, decomposeStretchBulletPrimitive)); + mrOutliner.StripPortions(); + mrOutliner.SetDrawPortionHdl(Link<DrawPortionInfo*,void>()); + mrOutliner.SetDrawBulletHdl(Link<DrawBulletInfo*,void>()); + } + + drawinglayer::primitive2d::Primitive2DContainer extractPrimitive2DSequence(); + + void impCreateTextPortionPrimitive(const DrawPortionInfo& rInfo); + }; + + class DoCapitalsDrawPortionInfo : public SvxDoCapitals + { + private: + impTextBreakupHandler& m_rHandler; + const DrawPortionInfo& m_rInfo; + SvxFont m_aFont; + public: + DoCapitalsDrawPortionInfo(impTextBreakupHandler& rHandler, const DrawPortionInfo& rInfo) + : SvxDoCapitals(rInfo.maText, rInfo.mnTextStart, rInfo.mnTextLen) + , m_rHandler(rHandler) + , m_rInfo(rInfo) + , m_aFont(rInfo.mrFont) + { + assert(!m_rInfo.mpDXArray.empty()); + + /* turn all these off as they are handled outside subportions for the whole portion */ + m_aFont.SetTransparent(false); + m_aFont.SetUnderline(LINESTYLE_NONE); + m_aFont.SetOverline(LINESTYLE_NONE); + m_aFont.SetStrikeout(STRIKEOUT_NONE); + + m_aFont.SetCaseMap(SvxCaseMap::NotMapped); /* otherwise this would call itself */ + } + virtual void Do( const OUString &rSpanTxt, const sal_Int32 nSpanIdx, + const sal_Int32 nSpanLen, const bool bUpper ) override + { + sal_uInt8 nProp(0); + if (!bUpper) + { + nProp = m_aFont.GetPropr(); + m_aFont.SetProprRel(SMALL_CAPS_PERCENTAGE); + } + + sal_Int32 nStartOffset = nSpanIdx - nIdx; + sal_Int32 nStartX = nStartOffset ? m_rInfo.mpDXArray[nStartOffset - 1] : 0; + + Point aStartPos(m_rInfo.mrStartPos.X() + nStartX, m_rInfo.mrStartPos.Y()); + + std::vector<sal_Int32> aDXArray; + aDXArray.reserve(nSpanLen); + for (sal_Int32 i = 0; i < nSpanLen; ++i) + aDXArray.push_back(m_rInfo.mpDXArray[nStartOffset + i] - nStartX); + + auto aKashidaArray = !m_rInfo.mpKashidaArray.empty() ? + std::span<const sal_Bool>(m_rInfo.mpKashidaArray.data() + nStartOffset, nSpanLen) : + std::span<const sal_Bool>(); + + DrawPortionInfo aInfo(aStartPos, rSpanTxt, + nSpanIdx, nSpanLen, + m_aFont, m_rInfo.mnPara, + aDXArray, aKashidaArray, + nullptr, /* no spelling in subportion, handled outside */ + nullptr, /* no field in subportion, handled outside */ + m_rInfo.mpLocale, m_rInfo.maOverlineColor, m_rInfo.maTextLineColor, + m_rInfo.mnBiDiLevel, false, 0, false, false, false); + + m_rHandler.impCreateTextPortionPrimitive(aInfo); + + if (!bUpper) + m_aFont.SetPropr(nProp); + } + }; + + void impTextBreakupHandler::impCreateTextPortionPrimitive(const DrawPortionInfo& rInfo) + { + if(rInfo.maText.isEmpty() || !rInfo.mnTextLen) + return; + + basegfx::B2DVector aFontScaling; + drawinglayer::attribute::FontAttribute aFontAttribute( + drawinglayer::primitive2d::getFontAttributeFromVclFont( + aFontScaling, + rInfo.mrFont, + rInfo.IsRTL(), + false)); + basegfx::B2DHomMatrix aNewTransform; + + // add font scale to new transform + aNewTransform.scale(aFontScaling.getX(), aFontScaling.getY()); + + // look for proportional font scaling, if necessary, scale accordingly + sal_Int8 nPropr(rInfo.mrFont.GetPropr()); + const double fPropFontFactor(nPropr / 100.0); + if (100 != nPropr) + aNewTransform.scale(fPropFontFactor, fPropFontFactor); + + // apply font rotate + if(rInfo.mrFont.GetOrientation()) + { + aNewTransform.rotate(-toRadians(rInfo.mrFont.GetOrientation())); + } + + // look for escapement, if necessary, translate accordingly + if(rInfo.mrFont.GetEscapement()) + { + sal_Int16 nEsc(rInfo.mrFont.GetEscapement()); + + if(DFLT_ESC_AUTO_SUPER == nEsc) + { + nEsc = .8 * (100 - nPropr); + assert (nEsc == DFLT_ESC_SUPER && "I'm sure this formula needs to be changed, but how to confirm that???"); + nEsc = DFLT_ESC_SUPER; + } + else if(DFLT_ESC_AUTO_SUB == nEsc) + { + nEsc = .2 * -(100 - nPropr); + assert (nEsc == -20 && "I'm sure this formula needs to be changed, but how to confirm that???"); + nEsc = -20; + } + + if(nEsc > MAX_ESC_POS) + { + nEsc = MAX_ESC_POS; + } + else if(nEsc < -MAX_ESC_POS) + { + nEsc = -MAX_ESC_POS; + } + + const double fEscapement(nEsc / -100.0); + aNewTransform.translate(0.0, fEscapement * aFontScaling.getY()); + } + + // apply transformA + aNewTransform *= maNewTransformA; + + // apply local offset + aNewTransform.translate(rInfo.mrStartPos.X(), rInfo.mrStartPos.Y()); + + // also apply embedding object's transform + aNewTransform *= maNewTransformB; + + // prepare DXArray content. To make it independent from font size (and such from + // the text transformation), scale it to unit coordinates + ::std::vector< double > aDXArray; + + if (!rInfo.mpDXArray.empty()) + { + aDXArray.reserve(rInfo.mnTextLen); + for(sal_Int32 a=0; a < rInfo.mnTextLen; a++) + { + aDXArray.push_back(static_cast<double>(rInfo.mpDXArray[a])); + } + } + + OUString caseMappedText = rInfo.mrFont.CalcCaseMap(rInfo.maText); + rtl::Reference<drawinglayer::primitive2d::BasePrimitive2D> pNewPrimitive(buildTextPortionPrimitive(rInfo, caseMappedText, + aFontAttribute, + aDXArray, aNewTransform)); + + bool bSmallCaps = rInfo.mrFont.IsCapital(); + if (bSmallCaps && rInfo.mpDXArray.empty()) + { + SAL_WARN("svx", "SmallCaps requested with DXArray, abandoning"); + bSmallCaps = false; + } + if (bSmallCaps) + { + // rerun with each sub-portion + DoCapitalsDrawPortionInfo aDoDrawPortionInfo(*this, rInfo); + rInfo.mrFont.DoOnCapitals(aDoDrawPortionInfo); + + // transfer collected primitives from maTextPortionPrimitives to a new container + drawinglayer::primitive2d::Primitive2DContainer aContainer; + aContainer.swap(maTextPortionPrimitives); + + // Take any decoration for the whole formatted portion and keep it to get continuous over/under/strike-through + if (pNewPrimitive->getPrimitive2DID() == PRIMITIVE2D_ID_TEXTDECORATEDPORTIONPRIMITIVE2D) + { + const drawinglayer::primitive2d::TextDecoratedPortionPrimitive2D* pTCPP = + static_cast<const drawinglayer::primitive2d::TextDecoratedPortionPrimitive2D*>(pNewPrimitive.get()); + + pTCPP->CreateDecorationGeometryContent( + aContainer, + pTCPP->getTextTransform(), + caseMappedText, + rInfo.mnTextStart, + rInfo.mnTextLen, + aDXArray); + } + + pNewPrimitive = new drawinglayer::primitive2d::GroupPrimitive2D(std::move(aContainer)); + } + + const Color aFontColor(rInfo.mrFont.GetColor()); + if (aFontColor.IsTransparent()) + { + // Handle semi-transparent text for both the decorated and simple case here. + pNewPrimitive = new drawinglayer::primitive2d::UnifiedTransparencePrimitive2D( + drawinglayer::primitive2d::Primitive2DContainer{ pNewPrimitive }, + (255 - aFontColor.GetAlpha()) / 255.0); + } + + if(rInfo.mbEndOfBullet) + { + // embed in TextHierarchyBulletPrimitive2D + drawinglayer::primitive2d::Primitive2DReference aNewReference(pNewPrimitive); + drawinglayer::primitive2d::Primitive2DContainer aNewSequence { aNewReference } ; + pNewPrimitive = new drawinglayer::primitive2d::TextHierarchyBulletPrimitive2D(std::move(aNewSequence)); + } + + if(rInfo.mpFieldData) + { + pNewPrimitive = impCheckFieldPrimitive(pNewPrimitive.get(), rInfo); + } + + maTextPortionPrimitives.push_back(pNewPrimitive); + + // support for WrongSpellVector. Create WrongSpellPrimitives as needed + if(!rInfo.mpWrongSpellVector || aDXArray.empty()) + return; + + const sal_Int32 nSize(rInfo.mpWrongSpellVector->size()); + const sal_Int32 nDXCount(aDXArray.size()); + const basegfx::BColor aSpellColor(1.0, 0.0, 0.0); // red, hard coded + + for(sal_Int32 a(0); a < nSize; a++) + { + const EEngineData::WrongSpellClass& rCandidate = (*rInfo.mpWrongSpellVector)[a]; + + if(rCandidate.nStart >= rInfo.mnTextStart && rCandidate.nEnd >= rInfo.mnTextStart && rCandidate.nEnd > rCandidate.nStart) + { + const sal_Int32 nStart(rCandidate.nStart - rInfo.mnTextStart); + const sal_Int32 nEnd(rCandidate.nEnd - rInfo.mnTextStart); + double fStart(0.0); + double fEnd(0.0); + + if(nStart > 0 && nStart - 1 < nDXCount) + { + fStart = aDXArray[nStart - 1]; + } + + if(nEnd > 0 && nEnd - 1 < nDXCount) + { + fEnd = aDXArray[nEnd - 1]; + } + + if(!basegfx::fTools::equal(fStart, fEnd)) + { + if(rInfo.IsRTL()) + { + // #i98523# + // When the portion is RTL, mirror the redlining using the + // full portion width + const double fTextWidth(aDXArray[aDXArray.size() - 1]); + + fStart = fTextWidth - fStart; + fEnd = fTextWidth - fEnd; + + // tdf#151968 + // if start < end, OutputDevice::DrawWaveLine() will + // think it is a rotated line, so we swap fStart and + // fEnd to avoid this. + std::swap(fStart, fEnd); + } + + // need to take FontScaling out of values; it's already part of + // aNewTransform and would be double applied + const double fFontScaleX(aFontScaling.getX() * fPropFontFactor); + + if(!basegfx::fTools::equal(fFontScaleX, 1.0) + && !basegfx::fTools::equalZero(fFontScaleX)) + { + fStart /= fFontScaleX; + fEnd /= fFontScaleX; + } + + maTextPortionPrimitives.push_back(new drawinglayer::primitive2d::WrongSpellPrimitive2D( + aNewTransform, + fStart, + fEnd, + aSpellColor)); + } + } + } + } + + rtl::Reference<drawinglayer::primitive2d::BasePrimitive2D> buildTextPortionPrimitive( + const DrawPortionInfo& rInfo, const OUString& rText, + const drawinglayer::attribute::FontAttribute& rFontAttribute, + const std::vector<double>& rDXArray, + const basegfx::B2DHomMatrix& rNewTransform) + { + ::std::vector< sal_Bool > aKashidaArray; + + if(!rInfo.mpKashidaArray.empty() && rInfo.mnTextLen) + { + aKashidaArray.reserve(rInfo.mnTextLen); + + for(sal_Int32 a=0; a < rInfo.mnTextLen; a++) + { + aKashidaArray.push_back(rInfo.mpKashidaArray[a]); + } + } + + // create complex text primitive and append + const Color aFontColor(rInfo.mrFont.GetColor()); + const basegfx::BColor aBFontColor(aFontColor.getBColor()); + + const Color aTextFillColor(rInfo.mrFont.GetFillColor()); + + // prepare wordLineMode (for underline and strikeout) + // NOT for bullet texts. It is set (this may be an error by itself), but needs to be suppressed to hinder e.g. '1)' + // to be split which would not look like the original + const bool bWordLineMode(rInfo.mrFont.IsWordLineMode() && !rInfo.mbEndOfBullet); + + // prepare new primitive + rtl::Reference<drawinglayer::primitive2d::BasePrimitive2D> pNewPrimitive; + const bool bDecoratedIsNeeded( + LINESTYLE_NONE != rInfo.mrFont.GetOverline() + || LINESTYLE_NONE != rInfo.mrFont.GetUnderline() + || STRIKEOUT_NONE != rInfo.mrFont.GetStrikeout() + || FontEmphasisMark::NONE != (rInfo.mrFont.GetEmphasisMark() & FontEmphasisMark::Style) + || FontRelief::NONE != rInfo.mrFont.GetRelief() + || rInfo.mrFont.IsShadow() + || bWordLineMode); + + if(bDecoratedIsNeeded) + { + // TextDecoratedPortionPrimitive2D needed, prepare some more data + // get overline and underline color. If it's on automatic (0xffffffff) use FontColor instead + const Color aUnderlineColor(rInfo.maTextLineColor); + const basegfx::BColor aBUnderlineColor((aUnderlineColor == COL_AUTO) ? aBFontColor : aUnderlineColor.getBColor()); + const Color aOverlineColor(rInfo.maOverlineColor); + const basegfx::BColor aBOverlineColor((aOverlineColor == COL_AUTO) ? aBFontColor : aOverlineColor.getBColor()); + + // prepare overline and underline data + const drawinglayer::primitive2d::TextLine eFontOverline( + drawinglayer::primitive2d::mapFontLineStyleToTextLine(rInfo.mrFont.GetOverline())); + const drawinglayer::primitive2d::TextLine eFontLineStyle( + drawinglayer::primitive2d::mapFontLineStyleToTextLine(rInfo.mrFont.GetUnderline())); + + // check UnderlineAbove + const bool bUnderlineAbove( + drawinglayer::primitive2d::TEXT_LINE_NONE != eFontLineStyle && rInfo.mrFont.IsUnderlineAbove()); + + // prepare strikeout data + const drawinglayer::primitive2d::TextStrikeout eTextStrikeout( + drawinglayer::primitive2d::mapFontStrikeoutToTextStrikeout(rInfo.mrFont.GetStrikeout())); + + // prepare emphasis mark data + drawinglayer::primitive2d::TextEmphasisMark eTextEmphasisMark(drawinglayer::primitive2d::TEXT_FONT_EMPHASIS_MARK_NONE); + + switch(rInfo.mrFont.GetEmphasisMark() & FontEmphasisMark::Style) + { + case FontEmphasisMark::Dot : eTextEmphasisMark = drawinglayer::primitive2d::TEXT_FONT_EMPHASIS_MARK_DOT; break; + case FontEmphasisMark::Circle : eTextEmphasisMark = drawinglayer::primitive2d::TEXT_FONT_EMPHASIS_MARK_CIRCLE; break; + case FontEmphasisMark::Disc : eTextEmphasisMark = drawinglayer::primitive2d::TEXT_FONT_EMPHASIS_MARK_DISC; break; + case FontEmphasisMark::Accent : eTextEmphasisMark = drawinglayer::primitive2d::TEXT_FONT_EMPHASIS_MARK_ACCENT; break; + default: break; + } + + const bool bEmphasisMarkAbove(rInfo.mrFont.GetEmphasisMark() & FontEmphasisMark::PosAbove); + const bool bEmphasisMarkBelow(rInfo.mrFont.GetEmphasisMark() & FontEmphasisMark::PosBelow); + + // prepare font relief data + drawinglayer::primitive2d::TextRelief eTextRelief(drawinglayer::primitive2d::TEXT_RELIEF_NONE); + + switch(rInfo.mrFont.GetRelief()) + { + case FontRelief::Embossed : eTextRelief = drawinglayer::primitive2d::TEXT_RELIEF_EMBOSSED; break; + case FontRelief::Engraved : eTextRelief = drawinglayer::primitive2d::TEXT_RELIEF_ENGRAVED; break; + default : break; // RELIEF_NONE, FontRelief_FORCE_EQUAL_SIZE + } + + // prepare shadow/outline data + const bool bShadow(rInfo.mrFont.IsShadow()); + + // TextDecoratedPortionPrimitive2D is needed, create one + pNewPrimitive = new drawinglayer::primitive2d::TextDecoratedPortionPrimitive2D( + + // attributes for TextSimplePortionPrimitive2D + rNewTransform, + rText, + rInfo.mnTextStart, + rInfo.mnTextLen, + std::vector(rDXArray), + std::vector(aKashidaArray), + rFontAttribute, + rInfo.mpLocale ? *rInfo.mpLocale : css::lang::Locale(), + aBFontColor, + aTextFillColor, + + // attributes for TextDecoratedPortionPrimitive2D + aBOverlineColor, + aBUnderlineColor, + eFontOverline, + eFontLineStyle, + bUnderlineAbove, + eTextStrikeout, + bWordLineMode, + eTextEmphasisMark, + bEmphasisMarkAbove, + bEmphasisMarkBelow, + eTextRelief, + bShadow); + } + else + { + // TextSimplePortionPrimitive2D is enough + pNewPrimitive = new drawinglayer::primitive2d::TextSimplePortionPrimitive2D( + rNewTransform, + rText, + rInfo.mnTextStart, + rInfo.mnTextLen, + std::vector(rDXArray), + std::vector(aKashidaArray), + rFontAttribute, + rInfo.mpLocale ? *rInfo.mpLocale : css::lang::Locale(), + aBFontColor, + rInfo.mbFilled, + rInfo.mnWidthToFill, + aTextFillColor); + } + + return pNewPrimitive; + } + + rtl::Reference<drawinglayer::primitive2d::BasePrimitive2D> impTextBreakupHandler::impCheckFieldPrimitive(drawinglayer::primitive2d::BasePrimitive2D* pPrimitive, const DrawPortionInfo& rInfo) + { + rtl::Reference<drawinglayer::primitive2d::BasePrimitive2D> xRet = pPrimitive; + if(rInfo.mpFieldData) + { + // Support for FIELD_SEQ_BEGIN, FIELD_SEQ_END. If used, create a TextHierarchyFieldPrimitive2D + // which holds the field type and, if applicable, the URL + const SvxURLField* pURLField = dynamic_cast< const SvxURLField* >(rInfo.mpFieldData); + const SvxPageField* pPageField = dynamic_cast< const SvxPageField* >(rInfo.mpFieldData); + + // embed current primitive to a sequence + drawinglayer::primitive2d::Primitive2DContainer aSequence; + + if(pPrimitive) + { + aSequence.resize(1); + aSequence[0] = drawinglayer::primitive2d::Primitive2DReference(pPrimitive); + } + + if(pURLField) + { + // extended this to hold more of the contents of the original + // SvxURLField since that stuff is still used in HitTest and e.g. Calc + std::vector< std::pair< OUString, OUString>> meValues; + meValues.emplace_back("URL", pURLField->GetURL()); + meValues.emplace_back("Representation", pURLField->GetRepresentation()); + meValues.emplace_back("TargetFrame", pURLField->GetTargetFrame()); + meValues.emplace_back("SvxURLFormat", OUString::number(static_cast<sal_uInt16>(pURLField->GetFormat()))); + xRet = new drawinglayer::primitive2d::TextHierarchyFieldPrimitive2D(std::move(aSequence), drawinglayer::primitive2d::FIELD_TYPE_URL, &meValues); + } + else if(pPageField) + { + xRet = new drawinglayer::primitive2d::TextHierarchyFieldPrimitive2D(std::move(aSequence), drawinglayer::primitive2d::FIELD_TYPE_PAGE); + } + else + { + xRet = new drawinglayer::primitive2d::TextHierarchyFieldPrimitive2D(std::move(aSequence), drawinglayer::primitive2d::FIELD_TYPE_COMMON); + } + } + + return xRet; + } + + void impTextBreakupHandler::impFlushTextPortionPrimitivesToLinePrimitives() + { + // only create a line primitive when we had content; there is no need for + // empty line primitives (contrary to paragraphs, see below). + if(!maTextPortionPrimitives.empty()) + { + maLinePrimitives.push_back(new drawinglayer::primitive2d::TextHierarchyLinePrimitive2D(std::move(maTextPortionPrimitives))); + } + } + + void impTextBreakupHandler::impFlushLinePrimitivesToParagraphPrimitives(sal_Int32 nPara) + { + sal_Int16 nDepth = mrOutliner.GetDepth(nPara); + EBulletInfo eInfo = mrOutliner.GetBulletInfo(nPara); + // Pass -1 to signal VclMetafileProcessor2D that there is no active + // bullets/numbering in this paragraph (i.e. this is normal text) + const sal_Int16 nOutlineLevel( eInfo.bVisible ? nDepth : -1); + + // ALWAYS create a paragraph primitive, even when no content was added. This is done to + // have the correct paragraph count even with empty paragraphs. Those paragraphs will + // have an empty sub-PrimitiveSequence. + maParagraphPrimitives.push_back( + new drawinglayer::primitive2d::TextHierarchyParagraphPrimitive2D( + std::move(maLinePrimitives), + nOutlineLevel)); + } + + void impTextBreakupHandler::impHandleDrawPortionInfo(const DrawPortionInfo& rInfo) + { + impCreateTextPortionPrimitive(rInfo); + + if(rInfo.mbEndOfLine || rInfo.mbEndOfParagraph) + { + impFlushTextPortionPrimitivesToLinePrimitives(); + } + + if(rInfo.mbEndOfParagraph) + { + impFlushLinePrimitivesToParagraphPrimitives(rInfo.mnPara); + } + } + + void impTextBreakupHandler::impHandleDrawBulletInfo(const DrawBulletInfo& rInfo) + { + basegfx::B2DHomMatrix aNewTransform; + + // add size to new transform + aNewTransform.scale(rInfo.maBulletSize.getWidth(), rInfo.maBulletSize.getHeight()); + + // apply transformA + aNewTransform *= maNewTransformA; + + // apply local offset + aNewTransform.translate(rInfo.maBulletPosition.X(), rInfo.maBulletPosition.Y()); + + // also apply embedding object's transform + aNewTransform *= maNewTransformB; + + // prepare empty GraphicAttr + const GraphicAttr aGraphicAttr; + + // create GraphicPrimitive2D + const drawinglayer::primitive2d::Primitive2DReference aNewReference(new drawinglayer::primitive2d::GraphicPrimitive2D( + aNewTransform, + rInfo.maBulletGraphicObject, + aGraphicAttr)); + + // embed in TextHierarchyBulletPrimitive2D + drawinglayer::primitive2d::Primitive2DContainer aNewSequence { aNewReference }; + rtl::Reference<drawinglayer::primitive2d::BasePrimitive2D> pNewPrimitive = new drawinglayer::primitive2d::TextHierarchyBulletPrimitive2D(std::move(aNewSequence)); + + // add to output + maTextPortionPrimitives.push_back(pNewPrimitive); + } + + IMPL_LINK(impTextBreakupHandler, decomposeContourTextPrimitive, DrawPortionInfo*, pInfo, void) + { + // for contour text, ignore (clip away) all portions which are below + // the visible area given by maScale + if(pInfo && static_cast<double>(pInfo->mrStartPos.Y()) < maScale.getY()) + { + impHandleDrawPortionInfo(*pInfo); + } + } + + IMPL_LINK(impTextBreakupHandler, decomposeBlockTextPrimitive, DrawPortionInfo*, pInfo, void) + { + if(!pInfo) + return; + + // Is clipping wanted? This is text clipping; only accept a portion + // if it's completely in the range + if(!maClipRange.isEmpty()) + { + // Test start position first; this allows to not get the text range at + // all if text is far outside + const basegfx::B2DPoint aStartPosition(pInfo->mrStartPos.X(), pInfo->mrStartPos.Y()); + + if(!maClipRange.isInside(aStartPosition)) + { + return; + } + + // Start position is inside. Get TextBoundRect and TopLeft next + drawinglayer::primitive2d::TextLayouterDevice aTextLayouterDevice; + aTextLayouterDevice.setFont(pInfo->mrFont); + + const basegfx::B2DRange aTextBoundRect( + aTextLayouterDevice.getTextBoundRect( + pInfo->maText, pInfo->mnTextStart, pInfo->mnTextLen)); + const basegfx::B2DPoint aTopLeft(aTextBoundRect.getMinimum() + aStartPosition); + + if(!maClipRange.isInside(aTopLeft)) + { + return; + } + + // TopLeft is inside. Get BottomRight and check + const basegfx::B2DPoint aBottomRight(aTextBoundRect.getMaximum() + aStartPosition); + + if(!maClipRange.isInside(aBottomRight)) + { + return; + } + + // all inside, clip was successful + } + impHandleDrawPortionInfo(*pInfo); + } + + IMPL_LINK(impTextBreakupHandler, decomposeStretchTextPrimitive, DrawPortionInfo*, pInfo, void) + { + if(pInfo) + { + impHandleDrawPortionInfo(*pInfo); + } + } + + IMPL_LINK(impTextBreakupHandler, decomposeContourBulletPrimitive, DrawBulletInfo*, pInfo, void) + { + if(pInfo) + { + impHandleDrawBulletInfo(*pInfo); + } + } + + IMPL_LINK(impTextBreakupHandler, decomposeBlockBulletPrimitive, DrawBulletInfo*, pInfo, void) + { + if(pInfo) + { + impHandleDrawBulletInfo(*pInfo); + } + } + + IMPL_LINK(impTextBreakupHandler, decomposeStretchBulletPrimitive, DrawBulletInfo*, pInfo, void) + { + if(pInfo) + { + impHandleDrawBulletInfo(*pInfo); + } + } + + drawinglayer::primitive2d::Primitive2DContainer impTextBreakupHandler::extractPrimitive2DSequence() + { + if(!maTextPortionPrimitives.empty()) + { + // collect non-closed lines + impFlushTextPortionPrimitivesToLinePrimitives(); + } + + if(!maLinePrimitives.empty()) + { + // collect non-closed paragraphs + impFlushLinePrimitivesToParagraphPrimitives(mrOutliner.GetParagraphCount() - 1); + } + + return std::move(maParagraphPrimitives); + } +} // end of anonymous namespace + + +// primitive decompositions + +void SdrTextObj::impDecomposeContourTextPrimitive( + drawinglayer::primitive2d::Primitive2DContainer& rTarget, + const drawinglayer::primitive2d::SdrContourTextPrimitive2D& rSdrContourTextPrimitive, + const drawinglayer::geometry::ViewInformation2D& aViewInformation) const +{ + // decompose matrix to have position and size of text + basegfx::B2DVector aScale, aTranslate; + double fRotate, fShearX; + rSdrContourTextPrimitive.getObjectTransform().decompose(aScale, aTranslate, fRotate, fShearX); + + // prepare contour polygon, force to non-mirrored for laying out + basegfx::B2DPolyPolygon aPolyPolygon(rSdrContourTextPrimitive.getUnitPolyPolygon()); + aPolyPolygon.transform(basegfx::utils::createScaleB2DHomMatrix(fabs(aScale.getX()), fabs(aScale.getY()))); + + // prepare outliner + SolarMutexGuard aSolarGuard; + SdrOutliner& rOutliner = ImpGetDrawOutliner(); + const Size aNullSize; + rOutliner.SetPaperSize(aNullSize); + rOutliner.SetPolygon(aPolyPolygon); + rOutliner.SetUpdateLayout(true); + rOutliner.SetText(rSdrContourTextPrimitive.getOutlinerParaObject()); + + // set visualizing page at Outliner; needed e.g. for PageNumberField decomposition + rOutliner.setVisualizedPage(GetSdrPageFromXDrawPage(aViewInformation.getVisualizedPage())); + + // prepare matrices to apply to newly created primitives + basegfx::B2DHomMatrix aNewTransformA; + + // mirroring. We are now in the polygon sizes. When mirroring in X and Y, + // move the null point which was top left to bottom right. + const bool bMirrorX(basegfx::fTools::less(aScale.getX(), 0.0)); + const bool bMirrorY(basegfx::fTools::less(aScale.getY(), 0.0)); + + // in-between the translations of the single primitives will take place. Afterwards, + // the object's transformations need to be applied + const basegfx::B2DHomMatrix aNewTransformB(basegfx::utils::createScaleShearXRotateTranslateB2DHomMatrix( + bMirrorX ? -1.0 : 1.0, bMirrorY ? -1.0 : 1.0, + fShearX, fRotate, aTranslate.getX(), aTranslate.getY())); + + // now break up text primitives. + impTextBreakupHandler aConverter(rOutliner); + aConverter.decomposeContourTextPrimitive(aNewTransformA, aNewTransformB, aScale); + + // cleanup outliner + rOutliner.Clear(); + rOutliner.setVisualizedPage(nullptr); + + rTarget = aConverter.extractPrimitive2DSequence(); +} + +void SdrTextObj::impDecomposeAutoFitTextPrimitive( + drawinglayer::primitive2d::Primitive2DContainer& rTarget, + const drawinglayer::primitive2d::SdrAutoFitTextPrimitive2D& rSdrAutofitTextPrimitive, + const drawinglayer::geometry::ViewInformation2D& aViewInformation) const +{ + // decompose matrix to have position and size of text + basegfx::B2DVector aScale, aTranslate; + double fRotate, fShearX; + rSdrAutofitTextPrimitive.getTextRangeTransform().decompose(aScale, aTranslate, fRotate, fShearX); + + // use B2DRange aAnchorTextRange for calculations + basegfx::B2DRange aAnchorTextRange(aTranslate); + aAnchorTextRange.expand(aTranslate + aScale); + + // prepare outliner + const SfxItemSet& rTextItemSet = rSdrAutofitTextPrimitive.getSdrText()->GetItemSet(); + SolarMutexGuard aSolarGuard; + SdrOutliner& rOutliner = ImpGetDrawOutliner(); + SdrTextVertAdjust eVAdj = GetTextVerticalAdjust(rTextItemSet); + SdrTextHorzAdjust eHAdj = GetTextHorizontalAdjust(rTextItemSet); + const EEControlBits nOriginalControlWord(rOutliner.GetControlWord()); + const Size aNullSize; + + // set visualizing page at Outliner; needed e.g. for PageNumberField decomposition + rOutliner.setVisualizedPage(GetSdrPageFromXDrawPage(aViewInformation.getVisualizedPage())); + + rOutliner.SetControlWord(nOriginalControlWord|EEControlBits::AUTOPAGESIZE|EEControlBits::STRETCHING); + rOutliner.SetMinAutoPaperSize(aNullSize); + rOutliner.SetMaxAutoPaperSize(Size(1000000,1000000)); + + // That color needs to be restored on leaving this method + Color aOriginalBackColor(rOutliner.GetBackgroundColor()); + setSuitableOutlinerBg(rOutliner); + + // add one to range sizes to get back to the old Rectangle and outliner measurements + const sal_uInt32 nAnchorTextWidth(FRound(aAnchorTextRange.getWidth() + 1)); + const sal_uInt32 nAnchorTextHeight(FRound(aAnchorTextRange.getHeight() + 1)); + const OutlinerParaObject* pOutlinerParaObject = rSdrAutofitTextPrimitive.getSdrText()->GetOutlinerParaObject(); + OSL_ENSURE(pOutlinerParaObject, "impDecomposeBlockTextPrimitive used with no OutlinerParaObject (!)"); + const bool bVerticalWriting(pOutlinerParaObject->IsEffectivelyVertical()); + const bool bTopToBottom(pOutlinerParaObject->IsTopToBottom()); + const Size aAnchorTextSize(Size(nAnchorTextWidth, nAnchorTextHeight)); + + if(rSdrAutofitTextPrimitive.getWordWrap() || IsTextFrame()) + { + rOutliner.SetMaxAutoPaperSize(aAnchorTextSize); + } + + if(SDRTEXTHORZADJUST_BLOCK == eHAdj && !bVerticalWriting) + { + rOutliner.SetMinAutoPaperSize(Size(nAnchorTextWidth, 0)); + rOutliner.SetMinColumnWrapHeight(nAnchorTextHeight); + } + + if(SDRTEXTVERTADJUST_BLOCK == eVAdj && bVerticalWriting) + { + rOutliner.SetMinAutoPaperSize(Size(0, nAnchorTextHeight)); + rOutliner.SetMinColumnWrapHeight(nAnchorTextWidth); + } + + rOutliner.SetPaperSize(aNullSize); + rOutliner.SetUpdateLayout(true); + rOutliner.SetText(*pOutlinerParaObject); + ImpAutoFitText(rOutliner,aAnchorTextSize,bVerticalWriting); + + // set visualizing page at Outliner; needed e.g. for PageNumberField decomposition + rOutliner.setVisualizedPage(GetSdrPageFromXDrawPage(aViewInformation.getVisualizedPage())); + + // now get back the layouted text size from outliner + const Size aOutlinerTextSize(rOutliner.GetPaperSize()); + const basegfx::B2DVector aOutlinerScale(aOutlinerTextSize.Width(), aOutlinerTextSize.Height()); + basegfx::B2DVector aAdjustTranslate(0.0, 0.0); + + // correct horizontal translation using the now known text size + if(SDRTEXTHORZADJUST_CENTER == eHAdj || SDRTEXTHORZADJUST_RIGHT == eHAdj) + { + const double fFree(aAnchorTextRange.getWidth() - aOutlinerScale.getX()); + + if(SDRTEXTHORZADJUST_CENTER == eHAdj) + { + aAdjustTranslate.setX(fFree / 2.0); + } + + if(SDRTEXTHORZADJUST_RIGHT == eHAdj) + { + aAdjustTranslate.setX(fFree); + } + } + + // correct vertical translation using the now known text size + if(SDRTEXTVERTADJUST_CENTER == eVAdj || SDRTEXTVERTADJUST_BOTTOM == eVAdj) + { + const double fFree(aAnchorTextRange.getHeight() - aOutlinerScale.getY()); + + if(SDRTEXTVERTADJUST_CENTER == eVAdj) + { + aAdjustTranslate.setY(fFree / 2.0); + } + + if(SDRTEXTVERTADJUST_BOTTOM == eVAdj) + { + aAdjustTranslate.setY(fFree); + } + } + + // prepare matrices to apply to newly created primitives. aNewTransformA + // will get coordinates in aOutlinerScale size and positive in X, Y. + basegfx::B2DHomMatrix aNewTransformA; + basegfx::B2DHomMatrix aNewTransformB; + + // translate relative to given primitive to get same rotation and shear + // as the master shape we are working on. For vertical, use the top-right + // corner + const double fStartInX(bVerticalWriting && bTopToBottom ? aAdjustTranslate.getX() + aOutlinerScale.getX() : aAdjustTranslate.getX()); + const double fStartInY(bVerticalWriting && !bTopToBottom ? aAdjustTranslate.getY() + aOutlinerScale.getY() : aAdjustTranslate.getY()); + aNewTransformA.translate(fStartInX, fStartInY); + + // mirroring. We are now in aAnchorTextRange sizes. When mirroring in X and Y, + // move the null point which was top left to bottom right. + const bool bMirrorX(basegfx::fTools::less(aScale.getX(), 0.0)); + const bool bMirrorY(basegfx::fTools::less(aScale.getY(), 0.0)); + aNewTransformB.scale(bMirrorX ? -1.0 : 1.0, bMirrorY ? -1.0 : 1.0); + + // in-between the translations of the single primitives will take place. Afterwards, + // the object's transformations need to be applied + aNewTransformB.shearX(fShearX); + aNewTransformB.rotate(fRotate); + aNewTransformB.translate(aTranslate.getX(), aTranslate.getY()); + + basegfx::B2DRange aClipRange; + + // now break up text primitives. + impTextBreakupHandler aConverter(rOutliner); + aConverter.decomposeBlockTextPrimitive(aNewTransformA, aNewTransformB, aClipRange); + + // cleanup outliner + rOutliner.SetBackgroundColor(aOriginalBackColor); + rOutliner.Clear(); + rOutliner.setVisualizedPage(nullptr); + rOutliner.SetControlWord(nOriginalControlWord); + + rTarget = aConverter.extractPrimitive2DSequence(); +} + +// Resolves: fdo#35779 set background color of this shape as the editeng background if there +// is one. Check the shape itself, then the host page, then that page's master page. +bool SdrObject::setSuitableOutlinerBg(::Outliner& rOutliner) const +{ + const SfxItemSet* pBackgroundFillSet = getBackgroundFillSet(); + if (drawing::FillStyle_NONE != pBackgroundFillSet->Get(XATTR_FILLSTYLE).GetValue()) + { + Color aColor(GetDraftFillColor(*pBackgroundFillSet).value_or(rOutliner.GetBackgroundColor())); + rOutliner.SetBackgroundColor(aColor); + return true; + } + return false; +} + +const SfxItemSet* SdrObject::getBackgroundFillSet() const +{ + const SfxItemSet* pBackgroundFillSet = &GetObjectItemSet(); + + if (drawing::FillStyle_NONE == pBackgroundFillSet->Get(XATTR_FILLSTYLE).GetValue()) + { + SdrPage* pOwnerPage(getSdrPageFromSdrObject()); + if (pOwnerPage) + { + pBackgroundFillSet = &pOwnerPage->getSdrPageProperties().GetItemSet(); + + if (drawing::FillStyle_NONE == pBackgroundFillSet->Get(XATTR_FILLSTYLE).GetValue()) + { + if (!pOwnerPage->IsMasterPage() && pOwnerPage->TRG_HasMasterPage()) + { + pBackgroundFillSet = &pOwnerPage->TRG_GetMasterPage().getSdrPageProperties().GetItemSet(); + } + } + } + } + return pBackgroundFillSet; +} + +const Graphic* SdrObject::getFillGraphic() const +{ + if(IsGroupObject()) // Doesn't make sense, and GetObjectItemSet() asserts. + return nullptr; + const SfxItemSet* pBackgroundFillSet = getBackgroundFillSet(); + if (drawing::FillStyle_BITMAP != pBackgroundFillSet->Get(XATTR_FILLSTYLE).GetValue()) + return nullptr; + return &pBackgroundFillSet->Get(XATTR_FILLBITMAP).GetGraphicObject().GetGraphic(); +} + +void SdrTextObj::impDecomposeBlockTextPrimitive( + drawinglayer::primitive2d::Primitive2DContainer& rTarget, + const drawinglayer::primitive2d::SdrBlockTextPrimitive2D& rSdrBlockTextPrimitive, + const drawinglayer::geometry::ViewInformation2D& aViewInformation) const +{ + // decompose matrix to have position and size of text + basegfx::B2DVector aScale, aTranslate; + double fRotate, fShearX; + rSdrBlockTextPrimitive.getTextRangeTransform().decompose(aScale, aTranslate, fRotate, fShearX); + + // use B2DRange aAnchorTextRange for calculations + basegfx::B2DRange aAnchorTextRange(aTranslate); + aAnchorTextRange.expand(aTranslate + aScale); + + // prepare outliner + const bool bIsCell(rSdrBlockTextPrimitive.getCellText()); + SolarMutexGuard aSolarGuard; + SdrOutliner& rOutliner = ImpGetDrawOutliner(); + SdrTextHorzAdjust eHAdj = rSdrBlockTextPrimitive.getSdrTextHorzAdjust(); + SdrTextVertAdjust eVAdj = rSdrBlockTextPrimitive.getSdrTextVertAdjust(); + const EEControlBits nOriginalControlWord(rOutliner.GetControlWord()); + const Size aNullSize; + + // set visualizing page at Outliner; needed e.g. for PageNumberField decomposition + rOutliner.setVisualizedPage(GetSdrPageFromXDrawPage(aViewInformation.getVisualizedPage())); + rOutliner.SetFixedCellHeight(rSdrBlockTextPrimitive.isFixedCellHeight()); + rOutliner.SetControlWord(nOriginalControlWord|EEControlBits::AUTOPAGESIZE); + rOutliner.SetMinAutoPaperSize(aNullSize); + rOutliner.SetMaxAutoPaperSize(Size(1000000,1000000)); + + // That color needs to be restored on leaving this method + Color aOriginalBackColor(rOutliner.GetBackgroundColor()); + setSuitableOutlinerBg(rOutliner); + + // add one to range sizes to get back to the old Rectangle and outliner measurements + const sal_uInt32 nAnchorTextWidth(FRound(aAnchorTextRange.getWidth() + 1)); + const sal_uInt32 nAnchorTextHeight(FRound(aAnchorTextRange.getHeight() + 1)); + const bool bVerticalWriting(rSdrBlockTextPrimitive.getOutlinerParaObject().IsEffectivelyVertical()); + const bool bTopToBottom(rSdrBlockTextPrimitive.getOutlinerParaObject().IsTopToBottom()); + const Size aAnchorTextSize(Size(nAnchorTextWidth, nAnchorTextHeight)); + + if(bIsCell) + { + // cell text is formatted neither like a text object nor like an object + // text, so use a special setup here + rOutliner.SetMaxAutoPaperSize(aAnchorTextSize); + + // #i106214# To work with an unchangeable PaperSize (CellSize in + // this case) Set(Min|Max)AutoPaperSize and SetPaperSize have to be used. + // #i106214# This was not completely correct; to still measure the real + // text height to allow vertical adjust (and vice versa for VerticalWritintg) + // only one aspect has to be set, but the other one to zero + if(bVerticalWriting) + { + // measure the horizontal text size + rOutliner.SetMinAutoPaperSize(Size(0, aAnchorTextSize.Height())); + } + else + { + // measure the vertical text size + rOutliner.SetMinAutoPaperSize(Size(aAnchorTextSize.Width(), 0)); + } + + rOutliner.SetPaperSize(aAnchorTextSize); + rOutliner.SetUpdateLayout(true); + rOutliner.SetText(rSdrBlockTextPrimitive.getOutlinerParaObject()); + } + else + { + // check if block text is used (only one of them can be true) + const bool bHorizontalIsBlock(SDRTEXTHORZADJUST_BLOCK == eHAdj && !bVerticalWriting); + const bool bVerticalIsBlock(SDRTEXTVERTADJUST_BLOCK == eVAdj && bVerticalWriting); + + // set minimal paper size horizontally/vertically if needed + if(bHorizontalIsBlock) + { + rOutliner.SetMinAutoPaperSize(Size(nAnchorTextWidth, 0)); + rOutliner.SetMinColumnWrapHeight(nAnchorTextHeight); + } + else if(bVerticalIsBlock) + { + rOutliner.SetMinAutoPaperSize(Size(0, nAnchorTextHeight)); + rOutliner.SetMinColumnWrapHeight(nAnchorTextWidth); + } + + if((rSdrBlockTextPrimitive.getWordWrap() || IsTextFrame()) && !rSdrBlockTextPrimitive.getUnlimitedPage()) + { + // #i103454# maximal paper size hor/ver needs to be limited to text + // frame size. If it's block text, still allow the 'other' direction + // to grow to get a correct real text size when using GetPaperSize(). + // When just using aAnchorTextSize as maximum, GetPaperSize() + // would just return aAnchorTextSize again: this means, the wanted + // 'measurement' of the real size of block text would not work + Size aMaxAutoPaperSize(aAnchorTextSize); + + // Usual processing - always grow in one of directions + bool bAllowGrowVertical = !bVerticalWriting; + bool bAllowGrowHorizontal = bVerticalWriting; + + // Compatibility mode for tdf#99729 + if (getSdrModelFromSdrObject().GetCompatibilityFlag( + SdrCompatibilityFlag::AnchoredTextOverflowLegacy)) + { + bAllowGrowVertical = bHorizontalIsBlock; + bAllowGrowHorizontal = bVerticalIsBlock; + } + + if (bAllowGrowVertical) + { + // allow to grow vertical for horizontal texts + aMaxAutoPaperSize.setHeight(1000000); + } + else if (bAllowGrowHorizontal) + { + // allow to grow horizontal for vertical texts + aMaxAutoPaperSize.setWidth(1000000); + } + + rOutliner.SetMaxAutoPaperSize(aMaxAutoPaperSize); + } + + rOutliner.SetPaperSize(aNullSize); + rOutliner.SetUpdateLayout(true); + rOutliner.SetText(rSdrBlockTextPrimitive.getOutlinerParaObject()); + } + + rOutliner.SetControlWord(nOriginalControlWord); + + // now get back the layouted text size from outliner + const Size aOutlinerTextSize(rOutliner.GetPaperSize()); + const basegfx::B2DVector aOutlinerScale(aOutlinerTextSize.Width(), aOutlinerTextSize.Height()); + basegfx::B2DVector aAdjustTranslate(0.0, 0.0); + + // For draw objects containing text correct hor/ver alignment if text is bigger + // than the object itself. Without that correction, the text would always be + // formatted to the left edge (or top edge when vertical) of the draw object. + if(!IsTextFrame() && !bIsCell) + { + if(aAnchorTextRange.getWidth() < aOutlinerScale.getX() && !bVerticalWriting) + { + // Horizontal case here. Correct only if eHAdj == SDRTEXTHORZADJUST_BLOCK, + // else the alignment is wanted. + if(SDRTEXTHORZADJUST_BLOCK == eHAdj) + { + SvxAdjust eAdjust = GetObjectItemSet().Get(EE_PARA_JUST).GetAdjust(); + switch(eAdjust) + { + case SvxAdjust::Left: eHAdj = SDRTEXTHORZADJUST_LEFT; break; + case SvxAdjust::Right: eHAdj = SDRTEXTHORZADJUST_RIGHT; break; + case SvxAdjust::Center: eHAdj = SDRTEXTHORZADJUST_CENTER; break; + default: break; + } + } + } + + if(aAnchorTextRange.getHeight() < aOutlinerScale.getY() && bVerticalWriting) + { + // Vertical case here. Correct only if eHAdj == SDRTEXTVERTADJUST_BLOCK, + // else the alignment is wanted. + if(SDRTEXTVERTADJUST_BLOCK == eVAdj) + { + eVAdj = SDRTEXTVERTADJUST_CENTER; + } + } + } + + // correct horizontal translation using the now known text size + if(SDRTEXTHORZADJUST_CENTER == eHAdj || SDRTEXTHORZADJUST_RIGHT == eHAdj) + { + const double fFree(aAnchorTextRange.getWidth() - aOutlinerScale.getX()); + + if(SDRTEXTHORZADJUST_CENTER == eHAdj) + { + aAdjustTranslate.setX(fFree / 2.0); + } + + if(SDRTEXTHORZADJUST_RIGHT == eHAdj) + { + aAdjustTranslate.setX(fFree); + } + } + + const double fFreeVerticalSpace(aAnchorTextRange.getHeight() - aOutlinerScale.getY()); + bool bClipVerticalTextOverflow = fFreeVerticalSpace < 0 + && GetObjectItemSet().Get(SDRATTR_TEXT_CLIPVERTOVERFLOW).GetValue(); + // correct vertical translation using the now known text size + if((SDRTEXTVERTADJUST_CENTER == eVAdj || SDRTEXTVERTADJUST_BOTTOM == eVAdj) + && !bClipVerticalTextOverflow) + { + if(SDRTEXTVERTADJUST_CENTER == eVAdj) + { + aAdjustTranslate.setY(fFreeVerticalSpace / 2.0); + } + + if(SDRTEXTVERTADJUST_BOTTOM == eVAdj) + { + aAdjustTranslate.setY(fFreeVerticalSpace); + } + } + + // prepare matrices to apply to newly created primitives. aNewTransformA + // will get coordinates in aOutlinerScale size and positive in X, Y. + // Translate relative to given primitive to get same rotation and shear + // as the master shape we are working on. For vertical, use the top-right + // corner + const double fStartInX(bVerticalWriting && bTopToBottom ? aAdjustTranslate.getX() + aOutlinerScale.getX() : aAdjustTranslate.getX()); + const double fStartInY(bVerticalWriting && !bTopToBottom ? aAdjustTranslate.getY() + aOutlinerScale.getY() : aAdjustTranslate.getY()); + basegfx::B2DHomMatrix aNewTransformA(basegfx::utils::createTranslateB2DHomMatrix(fStartInX, fStartInY)); + + // Apply the camera rotation. It have to be applied after adjustment of + // the text (top, bottom, center, left, right). + if(GetCameraZRotation() != 0) + { + // First find the text rect. + basegfx::B2DRange aTextRectangle(/*x1=*/aTranslate.getX() + aAdjustTranslate.getX(), + /*y1=*/aTranslate.getY() + aAdjustTranslate.getY(), + /*x2=*/aTranslate.getX() + aOutlinerScale.getX() - aAdjustTranslate.getX(), + /*y2=*/aTranslate.getY() + aOutlinerScale.getY() - aAdjustTranslate.getY()); + + // Rotate the text from the center point. + basegfx::B2DVector aTranslateToCenter(aTextRectangle.getWidth() / 2, aTextRectangle.getHeight() / 2); + aNewTransformA.translate(-aTranslateToCenter.getX(), -aTranslateToCenter.getY()); + aNewTransformA.rotate(basegfx::deg2rad(360.0 - GetCameraZRotation() )); + aNewTransformA.translate(aTranslateToCenter.getX(), aTranslateToCenter.getY()); + } + + // mirroring. We are now in aAnchorTextRange sizes. When mirroring in X and Y, + // move the null point which was top left to bottom right. + const bool bMirrorX(basegfx::fTools::less(aScale.getX(), 0.0)); + const bool bMirrorY(basegfx::fTools::less(aScale.getY(), 0.0)); + + // in-between the translations of the single primitives will take place. Afterwards, + // the object's transformations need to be applied + const basegfx::B2DHomMatrix aNewTransformB(basegfx::utils::createScaleShearXRotateTranslateB2DHomMatrix( + bMirrorX ? -1.0 : 1.0, bMirrorY ? -1.0 : 1.0, + fShearX, fRotate, aTranslate.getX(), aTranslate.getY())); + + + // create ClipRange (if needed) + basegfx::B2DRange aClipRange; + if(bClipVerticalTextOverflow) + aClipRange = {0, 0, std::numeric_limits<double>::max(), aAnchorTextRange.getHeight()}; + + // now break up text primitives. + impTextBreakupHandler aConverter(rOutliner); + aConverter.decomposeBlockTextPrimitive(aNewTransformA, aNewTransformB, aClipRange); + + // cleanup outliner + rOutliner.SetBackgroundColor(aOriginalBackColor); + rOutliner.Clear(); + rOutliner.setVisualizedPage(nullptr); + + rTarget = aConverter.extractPrimitive2DSequence(); +} + +void SdrTextObj::impDecomposeStretchTextPrimitive( + drawinglayer::primitive2d::Primitive2DContainer& rTarget, + const drawinglayer::primitive2d::SdrStretchTextPrimitive2D& rSdrStretchTextPrimitive, + const drawinglayer::geometry::ViewInformation2D& aViewInformation) const +{ + // decompose matrix to have position and size of text + basegfx::B2DVector aScale, aTranslate; + double fRotate, fShearX; + rSdrStretchTextPrimitive.getTextRangeTransform().decompose(aScale, aTranslate, fRotate, fShearX); + + // prepare outliner + SolarMutexGuard aSolarGuard; + SdrOutliner& rOutliner = ImpGetDrawOutliner(); + const EEControlBits nOriginalControlWord(rOutliner.GetControlWord()); + const Size aNullSize; + + rOutliner.SetControlWord(nOriginalControlWord|EEControlBits::STRETCHING|EEControlBits::AUTOPAGESIZE); + rOutliner.SetFixedCellHeight(rSdrStretchTextPrimitive.isFixedCellHeight()); + rOutliner.SetMinAutoPaperSize(aNullSize); + rOutliner.SetMaxAutoPaperSize(Size(1000000,1000000)); + rOutliner.SetPaperSize(aNullSize); + rOutliner.SetUpdateLayout(true); + rOutliner.SetText(rSdrStretchTextPrimitive.getOutlinerParaObject()); + + // set visualizing page at Outliner; needed e.g. for PageNumberField decomposition + rOutliner.setVisualizedPage(GetSdrPageFromXDrawPage(aViewInformation.getVisualizedPage())); + + // now get back the laid out text size from outliner + const Size aOutlinerTextSize(rOutliner.CalcTextSize()); + const basegfx::B2DVector aOutlinerScale( + aOutlinerTextSize.Width() == tools::Long(0) ? 1.0 : aOutlinerTextSize.Width(), + aOutlinerTextSize.Height() == tools::Long(0) ? 1.0 : aOutlinerTextSize.Height()); + + // prepare matrices to apply to newly created primitives + basegfx::B2DHomMatrix aNewTransformA; + + // #i101957# Check for vertical text. If used, aNewTransformA + // needs to translate the text initially around object width to orient + // it relative to the topper right instead of the topper left + const bool bVertical(rSdrStretchTextPrimitive.getOutlinerParaObject().IsEffectivelyVertical()); + const bool bTopToBottom(rSdrStretchTextPrimitive.getOutlinerParaObject().IsTopToBottom()); + + if(bVertical) + { + if(bTopToBottom) + aNewTransformA.translate(aScale.getX(), 0.0); + else + aNewTransformA.translate(0.0, aScale.getY()); + } + + // calculate global char stretching scale parameters. Use non-mirrored sizes + // to layout without mirroring + const double fScaleX(fabs(aScale.getX()) / aOutlinerScale.getX()); + const double fScaleY(fabs(aScale.getY()) / aOutlinerScale.getY()); + rOutliner.setGlobalScale(fScaleX * 100.0, fScaleY * 100.0, 100.0, 100.0); + + // When mirroring in X and Y, + // move the null point which was top left to bottom right. + const bool bMirrorX(basegfx::fTools::less(aScale.getX(), 0.0)); + const bool bMirrorY(basegfx::fTools::less(aScale.getY(), 0.0)); + + // in-between the translations of the single primitives will take place. Afterwards, + // the object's transformations need to be applied + const basegfx::B2DHomMatrix aNewTransformB(basegfx::utils::createScaleShearXRotateTranslateB2DHomMatrix( + bMirrorX ? -1.0 : 1.0, bMirrorY ? -1.0 : 1.0, + fShearX, fRotate, aTranslate.getX(), aTranslate.getY())); + + // now break up text primitives. + impTextBreakupHandler aConverter(rOutliner); + aConverter.decomposeStretchTextPrimitive(aNewTransformA, aNewTransformB); + + // cleanup outliner + rOutliner.SetControlWord(nOriginalControlWord); + rOutliner.Clear(); + rOutliner.setVisualizedPage(nullptr); + + rTarget = aConverter.extractPrimitive2DSequence(); +} + + +// timing generators +#define ENDLESS_LOOP (0xffffffff) +#define ENDLESS_TIME (double(0xffffffff)) +#define PIXEL_DPI (96.0) + +void SdrTextObj::impGetBlinkTextTiming(drawinglayer::animation::AnimationEntryList& rAnimList) const +{ + if(SdrTextAniKind::Blink != GetTextAniKind()) + return; + + // get values + const SfxItemSet& rSet = GetObjectItemSet(); + const sal_uInt32 nRepeat(static_cast<sal_uInt32>(rSet.Get(SDRATTR_TEXT_ANICOUNT).GetValue())); + double fDelay(static_cast<double>(rSet.Get(SDRATTR_TEXT_ANIDELAY).GetValue())); + + if(0.0 == fDelay) + { + // use default + fDelay = 250.0; + } + + // prepare loop and add + drawinglayer::animation::AnimationEntryLoop aLoop(nRepeat ? nRepeat : ENDLESS_LOOP); + drawinglayer::animation::AnimationEntryFixed aStart(fDelay, 0.0); + aLoop.append(aStart); + drawinglayer::animation::AnimationEntryFixed aEnd(fDelay, 1.0); + aLoop.append(aEnd); + rAnimList.append(aLoop); + + // add stopped state if loop is not endless + if(0 != nRepeat) + { + bool bVisibleWhenStopped(rSet.Get(SDRATTR_TEXT_ANISTOPINSIDE).GetValue()); + drawinglayer::animation::AnimationEntryFixed aStop(ENDLESS_TIME, bVisibleWhenStopped ? 0.0 : 1.0); + rAnimList.append(aStop); + } +} + +static void impCreateScrollTiming(const SfxItemSet& rSet, drawinglayer::animation::AnimationEntryList& rAnimList, bool bForward, double fTimeFullPath, double fFrequency) +{ + bool bVisibleWhenStopped(rSet.Get(SDRATTR_TEXT_ANISTOPINSIDE).GetValue()); + bool bVisibleWhenStarted(rSet.Get(SDRATTR_TEXT_ANISTARTINSIDE).GetValue()); + const sal_uInt32 nRepeat(rSet.Get(SDRATTR_TEXT_ANICOUNT).GetValue()); + + if(bVisibleWhenStarted) + { + // move from center to outside + drawinglayer::animation::AnimationEntryLinear aInOut(fTimeFullPath * 0.5, fFrequency, 0.5, bForward ? 1.0 : 0.0); + rAnimList.append(aInOut); + } + + // loop. In loop, move through + drawinglayer::animation::AnimationEntryLoop aLoop(nRepeat ? nRepeat : ENDLESS_LOOP); + drawinglayer::animation::AnimationEntryLinear aThrough(fTimeFullPath, fFrequency, bForward ? 0.0 : 1.0, bForward ? 1.0 : 0.0); + aLoop.append(aThrough); + rAnimList.append(aLoop); + + if(0 != nRepeat && bVisibleWhenStopped) + { + // move from outside to center + drawinglayer::animation::AnimationEntryLinear aOutIn(fTimeFullPath * 0.5, fFrequency, bForward ? 0.0 : 1.0, 0.5); + rAnimList.append(aOutIn); + + // add timing for staying at the end + drawinglayer::animation::AnimationEntryFixed aEnd(ENDLESS_TIME, 0.5); + rAnimList.append(aEnd); + } +} + +static void impCreateAlternateTiming(const SfxItemSet& rSet, drawinglayer::animation::AnimationEntryList& rAnimList, double fRelativeTextLength, bool bForward, double fTimeFullPath, double fFrequency) +{ + if(basegfx::fTools::more(fRelativeTextLength, 0.5)) + { + // this is the case when fTextLength > fFrameLength, text is bigger than animation frame. + // In that case, correct direction + bForward = !bForward; + } + + const double fStartPosition(bForward ? fRelativeTextLength : 1.0 - fRelativeTextLength); + const double fEndPosition(bForward ? 1.0 - fRelativeTextLength : fRelativeTextLength); + bool bVisibleWhenStarted(rSet.Get(SDRATTR_TEXT_ANISTARTINSIDE).GetValue()); + const sal_uInt32 nRepeat(rSet.Get(SDRATTR_TEXT_ANICOUNT).GetValue()); + + if(!bVisibleWhenStarted) + { + // move from outside to center + drawinglayer::animation::AnimationEntryLinear aOutIn(fTimeFullPath * 0.5, fFrequency, bForward ? 0.0 : 1.0, 0.5); + rAnimList.append(aOutIn); + } + + // loop. In loop, move out and in again. fInnerMovePath may be negative when text is bigger then frame, + // so use absolute value + const double fInnerMovePath(fabs(1.0 - (fRelativeTextLength * 2.0))); + const double fTimeForInnerPath(fTimeFullPath * fInnerMovePath); + const double fHalfInnerPath(fTimeForInnerPath * 0.5); + const sal_uInt32 nDoubleRepeat(nRepeat / 2L); + + if(nDoubleRepeat || 0 == nRepeat) + { + // double forth and back loop + drawinglayer::animation::AnimationEntryLoop aLoop(nDoubleRepeat ? nDoubleRepeat : ENDLESS_LOOP); + drawinglayer::animation::AnimationEntryLinear aTime0(fHalfInnerPath, fFrequency, 0.5, fEndPosition); + aLoop.append(aTime0); + drawinglayer::animation::AnimationEntryLinear aTime1(fTimeForInnerPath, fFrequency, fEndPosition, fStartPosition); + aLoop.append(aTime1); + drawinglayer::animation::AnimationEntryLinear aTime2(fHalfInnerPath, fFrequency, fStartPosition, 0.5); + aLoop.append(aTime2); + rAnimList.append(aLoop); + } + + if(nRepeat % 2L) + { + // repeat is uneven, so we need one more forth and back to center + drawinglayer::animation::AnimationEntryLinear aTime0(fHalfInnerPath, fFrequency, 0.5, fEndPosition); + rAnimList.append(aTime0); + drawinglayer::animation::AnimationEntryLinear aTime1(fHalfInnerPath, fFrequency, fEndPosition, 0.5); + rAnimList.append(aTime1); + } + + if(0 == nRepeat) + return; + + bool bVisibleWhenStopped(rSet.Get(SDRATTR_TEXT_ANISTOPINSIDE).GetValue()); + if(bVisibleWhenStopped) + { + // add timing for staying at the end + drawinglayer::animation::AnimationEntryFixed aEnd(ENDLESS_TIME, 0.5); + rAnimList.append(aEnd); + } + else + { + // move from center to outside + drawinglayer::animation::AnimationEntryLinear aInOut(fTimeFullPath * 0.5, fFrequency, 0.5, bForward ? 1.0 : 0.0); + rAnimList.append(aInOut); + } +} + +static void impCreateSlideTiming(const SfxItemSet& rSet, drawinglayer::animation::AnimationEntryList& rAnimList, bool bForward, double fTimeFullPath, double fFrequency) +{ + // move in from outside, start outside + const double fStartPosition(bForward ? 0.0 : 1.0); + const sal_uInt32 nRepeat(rSet.Get(SDRATTR_TEXT_ANICOUNT).GetValue()); + + // move from outside to center + drawinglayer::animation::AnimationEntryLinear aOutIn(fTimeFullPath * 0.5, fFrequency, fStartPosition, 0.5); + rAnimList.append(aOutIn); + + // loop. In loop, move out and in again + if(nRepeat > 1 || 0 == nRepeat) + { + drawinglayer::animation::AnimationEntryLoop aLoop(nRepeat ? nRepeat - 1 : ENDLESS_LOOP); + drawinglayer::animation::AnimationEntryLinear aTime0(fTimeFullPath * 0.5, fFrequency, 0.5, fStartPosition); + aLoop.append(aTime0); + drawinglayer::animation::AnimationEntryLinear aTime1(fTimeFullPath * 0.5, fFrequency, fStartPosition, 0.5); + aLoop.append(aTime1); + rAnimList.append(aLoop); + } + + // always visible when stopped, so add timing for staying at the end when not endless + if(0 != nRepeat) + { + drawinglayer::animation::AnimationEntryFixed aEnd(ENDLESS_TIME, 0.5); + rAnimList.append(aEnd); + } +} + +void SdrTextObj::impGetScrollTextTiming(drawinglayer::animation::AnimationEntryList& rAnimList, double fFrameLength, double fTextLength) const +{ + const SdrTextAniKind eAniKind(GetTextAniKind()); + + if(SdrTextAniKind::Scroll != eAniKind && SdrTextAniKind::Alternate != eAniKind && SdrTextAniKind::Slide != eAniKind) + return; + + // get data. Goal is to calculate fTimeFullPath which is the time needed to + // move animation from (0.0) to (1.0) state + const SfxItemSet& rSet = GetObjectItemSet(); + double fAnimationDelay(static_cast<double>(rSet.Get(SDRATTR_TEXT_ANIDELAY).GetValue())); + double fSingleStepWidth(static_cast<double>(rSet.Get(SDRATTR_TEXT_ANIAMOUNT).GetValue())); + const SdrTextAniDirection eDirection(GetTextAniDirection()); + const bool bForward(SdrTextAniDirection::Right == eDirection || SdrTextAniDirection::Down == eDirection); + + if(basegfx::fTools::equalZero(fAnimationDelay)) + { + // default to 1/20 second + fAnimationDelay = 50.0; + } + + if(basegfx::fTools::less(fSingleStepWidth, 0.0)) + { + // data is in pixels, convert to logic. Imply PIXEL_DPI dpi. + // It makes no sense to keep the view-transformation centered + // definitions, so get rid of them here. + fSingleStepWidth = (-fSingleStepWidth * (2540.0 / PIXEL_DPI)); + } + + if(basegfx::fTools::equalZero(fSingleStepWidth)) + { + // default to 1 millimeter + fSingleStepWidth = 100.0; + } + + // use the length of the full animation path and the number of steps + // to get the full path time + const double fFullPathLength(fFrameLength + fTextLength); + const double fNumberOfSteps(fFullPathLength / fSingleStepWidth); + double fTimeFullPath(fNumberOfSteps * fAnimationDelay); + + if(fTimeFullPath < fAnimationDelay) + { + fTimeFullPath = fAnimationDelay; + } + + switch(eAniKind) + { + case SdrTextAniKind::Scroll : + { + impCreateScrollTiming(rSet, rAnimList, bForward, fTimeFullPath, fAnimationDelay); + break; + } + case SdrTextAniKind::Alternate : + { + double fRelativeTextLength(fTextLength / (fFrameLength + fTextLength)); + impCreateAlternateTiming(rSet, rAnimList, fRelativeTextLength, bForward, fTimeFullPath, fAnimationDelay); + break; + } + case SdrTextAniKind::Slide : + { + impCreateSlideTiming(rSet, rAnimList, bForward, fTimeFullPath, fAnimationDelay); + break; + } + default : break; // SdrTextAniKind::NONE, SdrTextAniKind::Blink + } +} + +void SdrTextObj::impHandleChainingEventsDuringDecomposition(SdrOutliner &rOutliner) const +{ + if (GetTextChain()->GetNilChainingEvent(this)) + return; + + GetTextChain()->SetNilChainingEvent(this, true); + + TextChainFlow aTxtChainFlow(const_cast<SdrTextObj*>(this)); + bool bIsOverflow; + +#ifdef DBG_UTIL + // Some debug output + size_t nObjCount(getSdrPageFromSdrObject()->GetObjCount()); + for (size_t i = 0; i < nObjCount; i++) + { + SdrTextObj* pCurObj(DynCastSdrTextObj(getSdrPageFromSdrObject()->GetObj(i))); + if(pCurObj == this) + { + SAL_INFO("svx.chaining", "Working on TextBox " << i); + break; + } + } +#endif + + aTxtChainFlow.CheckForFlowEvents(&rOutliner); + + if (aTxtChainFlow.IsUnderflow() && !IsInEditMode()) + { + // underflow-induced overflow + aTxtChainFlow.ExecuteUnderflow(&rOutliner); + bIsOverflow = aTxtChainFlow.IsOverflow(); + } else { + // standard overflow (no underflow before) + bIsOverflow = aTxtChainFlow.IsOverflow(); + } + + if (bIsOverflow && !IsInEditMode()) { + // Initialize Chaining Outliner + SdrOutliner &rChainingOutl(getSdrModelFromSdrObject().GetChainingOutliner(this)); + ImpInitDrawOutliner( rChainingOutl ); + rChainingOutl.SetUpdateLayout(true); + // We must pass the chaining outliner otherwise we would mess up decomposition + aTxtChainFlow.ExecuteOverflow(&rOutliner, &rChainingOutl); + } + + GetTextChain()->SetNilChainingEvent(this, false); +} + +void SdrTextObj::impDecomposeChainedTextPrimitive( + drawinglayer::primitive2d::Primitive2DContainer& rTarget, + const drawinglayer::primitive2d::SdrChainedTextPrimitive2D& rSdrChainedTextPrimitive, + const drawinglayer::geometry::ViewInformation2D& aViewInformation) const +{ + // decompose matrix to have position and size of text + basegfx::B2DVector aScale, aTranslate; + double fRotate, fShearX; + rSdrChainedTextPrimitive.getTextRangeTransform().decompose(aScale, aTranslate, fRotate, fShearX); + + // use B2DRange aAnchorTextRange for calculations + basegfx::B2DRange aAnchorTextRange(aTranslate); + aAnchorTextRange.expand(aTranslate + aScale); + + // prepare outliner + const SfxItemSet& rTextItemSet = rSdrChainedTextPrimitive.getSdrText()->GetItemSet(); + SolarMutexGuard aSolarGuard; + SdrOutliner& rOutliner = ImpGetDrawOutliner(); + + SdrTextVertAdjust eVAdj = GetTextVerticalAdjust(rTextItemSet); + SdrTextHorzAdjust eHAdj = GetTextHorizontalAdjust(rTextItemSet); + const EEControlBits nOriginalControlWord(rOutliner.GetControlWord()); + const Size aNullSize; + + // set visualizing page at Outliner; needed e.g. for PageNumberField decomposition + rOutliner.setVisualizedPage(GetSdrPageFromXDrawPage(aViewInformation.getVisualizedPage())); + + rOutliner.SetControlWord(nOriginalControlWord|EEControlBits::AUTOPAGESIZE|EEControlBits::STRETCHING); + rOutliner.SetMinAutoPaperSize(aNullSize); + rOutliner.SetMaxAutoPaperSize(Size(1000000,1000000)); + + // add one to range sizes to get back to the old Rectangle and outliner measurements + const sal_uInt32 nAnchorTextWidth(FRound(aAnchorTextRange.getWidth() + 1)); + const sal_uInt32 nAnchorTextHeight(FRound(aAnchorTextRange.getHeight() + 1)); + + // Text + const OutlinerParaObject* pOutlinerParaObject = rSdrChainedTextPrimitive.getSdrText()->GetOutlinerParaObject(); + OSL_ENSURE(pOutlinerParaObject, "impDecomposeBlockTextPrimitive used with no OutlinerParaObject (!)"); + + const bool bVerticalWriting(pOutlinerParaObject->IsEffectivelyVertical()); + const bool bTopToBottom(pOutlinerParaObject->IsTopToBottom()); + const Size aAnchorTextSize(Size(nAnchorTextWidth, nAnchorTextHeight)); + + if(IsTextFrame()) + { + rOutliner.SetMaxAutoPaperSize(aAnchorTextSize); + } + + if(SDRTEXTHORZADJUST_BLOCK == eHAdj && !bVerticalWriting) + { + rOutliner.SetMinAutoPaperSize(Size(nAnchorTextWidth, 0)); + } + + if(SDRTEXTVERTADJUST_BLOCK == eVAdj && bVerticalWriting) + { + rOutliner.SetMinAutoPaperSize(Size(0, nAnchorTextHeight)); + } + + rOutliner.SetPaperSize(aNullSize); + rOutliner.SetUpdateLayout(true); + // Sets original text + rOutliner.SetText(*pOutlinerParaObject); + + /* Begin overflow/underflow handling */ + + impHandleChainingEventsDuringDecomposition(rOutliner); + + /* End overflow/underflow handling */ + + // set visualizing page at Outliner; needed e.g. for PageNumberField decomposition + rOutliner.setVisualizedPage(GetSdrPageFromXDrawPage(aViewInformation.getVisualizedPage())); + + // now get back the layouted text size from outliner + const Size aOutlinerTextSize(rOutliner.GetPaperSize()); + const basegfx::B2DVector aOutlinerScale(aOutlinerTextSize.Width(), aOutlinerTextSize.Height()); + basegfx::B2DVector aAdjustTranslate(0.0, 0.0); + + // correct horizontal translation using the now known text size + if(SDRTEXTHORZADJUST_CENTER == eHAdj || SDRTEXTHORZADJUST_RIGHT == eHAdj) + { + const double fFree(aAnchorTextRange.getWidth() - aOutlinerScale.getX()); + + if(SDRTEXTHORZADJUST_CENTER == eHAdj) + { + aAdjustTranslate.setX(fFree / 2.0); + } + + if(SDRTEXTHORZADJUST_RIGHT == eHAdj) + { + aAdjustTranslate.setX(fFree); + } + } + + // correct vertical translation using the now known text size + if(SDRTEXTVERTADJUST_CENTER == eVAdj || SDRTEXTVERTADJUST_BOTTOM == eVAdj) + { + const double fFree(aAnchorTextRange.getHeight() - aOutlinerScale.getY()); + + if(SDRTEXTVERTADJUST_CENTER == eVAdj) + { + aAdjustTranslate.setY(fFree / 2.0); + } + + if(SDRTEXTVERTADJUST_BOTTOM == eVAdj) + { + aAdjustTranslate.setY(fFree); + } + } + + // prepare matrices to apply to newly created primitives. aNewTransformA + // will get coordinates in aOutlinerScale size and positive in X, Y. + basegfx::B2DHomMatrix aNewTransformA; + basegfx::B2DHomMatrix aNewTransformB; + + // translate relative to given primitive to get same rotation and shear + // as the master shape we are working on. For vertical, use the top-right + // corner + const double fStartInX(bVerticalWriting && bTopToBottom ? aAdjustTranslate.getX() + aOutlinerScale.getX() : aAdjustTranslate.getX()); + const double fStartInY(bVerticalWriting && !bTopToBottom ? aAdjustTranslate.getY() + aOutlinerScale.getY() : aAdjustTranslate.getY()); + aNewTransformA.translate(fStartInX, fStartInY); + + // mirroring. We are now in aAnchorTextRange sizes. When mirroring in X and Y, + // move the null point which was top left to bottom right. + const bool bMirrorX(basegfx::fTools::less(aScale.getX(), 0.0)); + const bool bMirrorY(basegfx::fTools::less(aScale.getY(), 0.0)); + aNewTransformB.scale(bMirrorX ? -1.0 : 1.0, bMirrorY ? -1.0 : 1.0); + + // in-between the translations of the single primitives will take place. Afterwards, + // the object's transformations need to be applied + aNewTransformB.shearX(fShearX); + aNewTransformB.rotate(fRotate); + aNewTransformB.translate(aTranslate.getX(), aTranslate.getY()); + + basegfx::B2DRange aClipRange; + + // now break up text primitives. + impTextBreakupHandler aConverter(rOutliner); + aConverter.decomposeBlockTextPrimitive(aNewTransformA, aNewTransformB, aClipRange); + + // cleanup outliner + rOutliner.Clear(); + rOutliner.setVisualizedPage(nullptr); + rOutliner.SetControlWord(nOriginalControlWord); + + rTarget = aConverter.extractPrimitive2DSequence(); +} + +// Direct decomposer for text visualization when you already have a prepared +// Outliner containing all the needed information +void SdrTextObj::impDecomposeBlockTextPrimitiveDirect( + drawinglayer::primitive2d::Primitive2DContainer& rTarget, + SdrOutliner& rOutliner, + const basegfx::B2DHomMatrix& rNewTransformA, + const basegfx::B2DHomMatrix& rNewTransformB, + const basegfx::B2DRange& rClipRange) +{ + impTextBreakupHandler aConverter(rOutliner); + aConverter.decomposeBlockTextPrimitive(rNewTransformA, rNewTransformB, rClipRange); + rTarget.append(aConverter.extractPrimitive2DSequence()); +} + +double SdrTextObj::GetCameraZRotation() const +{ + const css::uno::Any* pAny; + double fTextCameraZRotateAngle = 0.0; + const SfxItemSet& rSet = GetObjectItemSet(); + const SdrCustomShapeGeometryItem& rGeometryItem(rSet.Get(SDRATTR_CUSTOMSHAPE_GEOMETRY)); + + pAny = rGeometryItem.GetPropertyValueByName("TextCameraZRotateAngle"); + + if ( pAny ) + *pAny >>= fTextCameraZRotateAngle; + + return fTextCameraZRotateAngle; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ |