tdf#154285 Check upper bound of arguments in SbRtl_Minute function
[LibreOffice.git] / svx / source / svdraw / svdotextdecomposition.cxx
blobc9fd31f0414885361ca0feeceef98c00be67756e
1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 /*
3 * This file is part of the LibreOffice project.
5 * This Source Code Form is subject to the terms of the Mozilla Public
6 * License, v. 2.0. If a copy of the MPL was not distributed with this
7 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
9 * This file incorporates work covered by the following license notice:
11 * Licensed to the Apache Software Foundation (ASF) under one or more
12 * contributor license agreements. See the NOTICE file distributed
13 * with this work for additional information regarding copyright
14 * ownership. The ASF licenses this file to you under the Apache
15 * License, Version 2.0 (the "License"); you may not use this file
16 * except in compliance with the License. You may obtain a copy of
17 * the License at http://www.apache.org/licenses/LICENSE-2.0 .
20 #include <svx/compatflags.hxx>
21 #include <svx/svdetc.hxx>
22 #include <svx/svdoutl.hxx>
23 #include <svx/svdpage.hxx>
24 #include <svx/svdotext.hxx>
25 #include <svx/svdmodel.hxx>
26 #include <svx/sdasitm.hxx>
27 #include <textchain.hxx>
28 #include <textchainflow.hxx>
29 #include <svx/sdtacitm.hxx>
30 #include <svx/sdtayitm.hxx>
31 #include <svx/sdtaiitm.hxx>
32 #include <svx/sdtaaitm.hxx>
33 #include <svx/xfillit0.hxx>
34 #include <svx/xbtmpit.hxx>
35 #include <basegfx/vector/b2dvector.hxx>
36 #include <sdr/primitive2d/sdrtextprimitive2d.hxx>
37 #include <drawinglayer/primitive2d/drawinglayer_primitivetypes2d.hxx>
38 #include <drawinglayer/primitive2d/textbreakuphelper.hxx>
39 #include <drawinglayer/primitive2d/textprimitive2d.hxx>
40 #include <drawinglayer/primitive2d/textdecoratedprimitive2d.hxx>
41 #include <basegfx/range/b2drange.hxx>
42 #include <editeng/eeitem.hxx>
43 #include <editeng/editstat.hxx>
44 #include <editeng/smallcaps.hxx>
45 #include <tools/helpers.hxx>
46 #include <svl/itemset.hxx>
47 #include <drawinglayer/animation/animationtiming.hxx>
48 #include <basegfx/color/bcolor.hxx>
49 #include <vcl/svapp.hxx>
50 #include <editeng/escapementitem.hxx>
51 #include <editeng/svxenum.hxx>
52 #include <editeng/flditem.hxx>
53 #include <editeng/adjustitem.hxx>
54 #include <drawinglayer/primitive2d/texthierarchyprimitive2d.hxx>
55 #include <drawinglayer/primitive2d/wrongspellprimitive2d.hxx>
56 #include <drawinglayer/primitive2d/graphicprimitive2d.hxx>
57 #include <drawinglayer/primitive2d/textlayoutdevice.hxx>
58 #include <drawinglayer/primitive2d/unifiedtransparenceprimitive2d.hxx>
59 #include <svx/unoapi.hxx>
60 #include <drawinglayer/geometry/viewinformation2d.hxx>
61 #include <editeng/outlobj.hxx>
62 #include <basegfx/matrix/b2dhommatrixtools.hxx>
63 #include <sal/log.hxx>
64 #include <osl/diagnose.h>
66 using namespace com::sun::star;
68 // helpers
70 namespace
72 rtl::Reference<drawinglayer::primitive2d::BasePrimitive2D> buildTextPortionPrimitive(const DrawPortionInfo& rInfo, const OUString& rText,
73 const drawinglayer::attribute::FontAttribute& rFontAttribute,
74 const std::vector<double>& rDXArray,
75 const basegfx::B2DHomMatrix& rNewTransform);
77 class impTextBreakupHandler
79 private:
80 drawinglayer::primitive2d::Primitive2DContainer maTextPortionPrimitives;
81 drawinglayer::primitive2d::Primitive2DContainer maLinePrimitives;
82 drawinglayer::primitive2d::Primitive2DContainer maParagraphPrimitives;
84 SdrOutliner& mrOutliner;
85 basegfx::B2DHomMatrix maNewTransformA;
86 basegfx::B2DHomMatrix maNewTransformB;
88 // the visible area for contour text decomposition
89 basegfx::B2DVector maScale;
91 // ClipRange for BlockText decomposition; only text portions completely
92 // inside are to be accepted, so this is different from geometric clipping
93 // (which would allow e.g. upper parts of portions to remain). Only used for
94 // BlockText (see there)
95 basegfx::B2DRange maClipRange;
97 DECL_LINK(decomposeContourTextPrimitive, DrawPortionInfo*, void);
98 DECL_LINK(decomposeBlockTextPrimitive, DrawPortionInfo*, void);
99 DECL_LINK(decomposeStretchTextPrimitive, DrawPortionInfo*, void);
101 DECL_LINK(decomposeContourBulletPrimitive, DrawBulletInfo*, void);
102 DECL_LINK(decomposeBlockBulletPrimitive, DrawBulletInfo*, void);
103 DECL_LINK(decomposeStretchBulletPrimitive, DrawBulletInfo*, void);
105 static rtl::Reference<drawinglayer::primitive2d::BasePrimitive2D> impCheckFieldPrimitive(drawinglayer::primitive2d::BasePrimitive2D* pPrimitive, const DrawPortionInfo& rInfo);
106 void impFlushTextPortionPrimitivesToLinePrimitives();
107 void impFlushLinePrimitivesToParagraphPrimitives(sal_Int32 nPara);
108 void impHandleDrawPortionInfo(const DrawPortionInfo& rInfo);
109 void impHandleDrawBulletInfo(const DrawBulletInfo& rInfo);
111 public:
112 explicit impTextBreakupHandler(SdrOutliner& rOutliner)
113 : mrOutliner(rOutliner)
117 void decomposeContourTextPrimitive(const basegfx::B2DHomMatrix& rNewTransformA, const basegfx::B2DHomMatrix& rNewTransformB, const basegfx::B2DVector& rScale)
119 maScale = rScale;
120 maNewTransformA = rNewTransformA;
121 maNewTransformB = rNewTransformB;
122 mrOutliner.SetDrawPortionHdl(LINK(this, impTextBreakupHandler, decomposeContourTextPrimitive));
123 mrOutliner.SetDrawBulletHdl(LINK(this, impTextBreakupHandler, decomposeContourBulletPrimitive));
124 mrOutliner.StripPortions();
125 mrOutliner.SetDrawPortionHdl(Link<DrawPortionInfo*,void>());
126 mrOutliner.SetDrawBulletHdl(Link<DrawBulletInfo*,void>());
129 void decomposeBlockTextPrimitive(
130 const basegfx::B2DHomMatrix& rNewTransformA,
131 const basegfx::B2DHomMatrix& rNewTransformB,
132 const basegfx::B2DRange& rClipRange)
134 maNewTransformA = rNewTransformA;
135 maNewTransformB = rNewTransformB;
136 maClipRange = rClipRange;
137 mrOutliner.SetDrawPortionHdl(LINK(this, impTextBreakupHandler, decomposeBlockTextPrimitive));
138 mrOutliner.SetDrawBulletHdl(LINK(this, impTextBreakupHandler, decomposeBlockBulletPrimitive));
139 mrOutliner.StripPortions();
140 mrOutliner.SetDrawPortionHdl(Link<DrawPortionInfo*,void>());
141 mrOutliner.SetDrawBulletHdl(Link<DrawBulletInfo*,void>());
144 void decomposeStretchTextPrimitive(const basegfx::B2DHomMatrix& rNewTransformA, const basegfx::B2DHomMatrix& rNewTransformB)
146 maNewTransformA = rNewTransformA;
147 maNewTransformB = rNewTransformB;
148 mrOutliner.SetDrawPortionHdl(LINK(this, impTextBreakupHandler, decomposeStretchTextPrimitive));
149 mrOutliner.SetDrawBulletHdl(LINK(this, impTextBreakupHandler, decomposeStretchBulletPrimitive));
150 mrOutliner.StripPortions();
151 mrOutliner.SetDrawPortionHdl(Link<DrawPortionInfo*,void>());
152 mrOutliner.SetDrawBulletHdl(Link<DrawBulletInfo*,void>());
155 drawinglayer::primitive2d::Primitive2DContainer extractPrimitive2DSequence();
157 void impCreateTextPortionPrimitive(const DrawPortionInfo& rInfo);
160 class DoCapitalsDrawPortionInfo : public SvxDoCapitals
162 private:
163 impTextBreakupHandler& m_rHandler;
164 const DrawPortionInfo& m_rInfo;
165 SvxFont m_aFont;
166 public:
167 DoCapitalsDrawPortionInfo(impTextBreakupHandler& rHandler, const DrawPortionInfo& rInfo)
168 : SvxDoCapitals(rInfo.maText, rInfo.mnTextStart, rInfo.mnTextLen)
169 , m_rHandler(rHandler)
170 , m_rInfo(rInfo)
171 , m_aFont(rInfo.mrFont)
173 assert(!m_rInfo.mpDXArray.empty());
175 /* turn all these off as they are handled outside subportions for the whole portion */
176 m_aFont.SetTransparent(false);
177 m_aFont.SetUnderline(LINESTYLE_NONE);
178 m_aFont.SetOverline(LINESTYLE_NONE);
179 m_aFont.SetStrikeout(STRIKEOUT_NONE);
181 m_aFont.SetCaseMap(SvxCaseMap::NotMapped); /* otherwise this would call itself */
183 virtual void Do( const OUString &rSpanTxt, const sal_Int32 nSpanIdx,
184 const sal_Int32 nSpanLen, const bool bUpper ) override
186 sal_uInt8 nProp(0);
187 if (!bUpper)
189 nProp = m_aFont.GetPropr();
190 m_aFont.SetProprRel(SMALL_CAPS_PERCENTAGE);
193 sal_Int32 nStartOffset = nSpanIdx - nIdx;
194 double nStartX = nStartOffset ? m_rInfo.mpDXArray[nStartOffset - 1] : 0;
196 Point aStartPos(m_rInfo.mrStartPos.X() + nStartX, m_rInfo.mrStartPos.Y());
198 KernArray aDXArray;
199 aDXArray.resize(nSpanLen);
200 for (sal_Int32 i = 0; i < nSpanLen; ++i)
201 aDXArray[i] = m_rInfo.mpDXArray[nStartOffset + i] - nStartX;
203 auto aKashidaArray = !m_rInfo.mpKashidaArray.empty() ?
204 std::span<const sal_Bool>(m_rInfo.mpKashidaArray.data() + nStartOffset, nSpanLen) :
205 std::span<const sal_Bool>();
207 DrawPortionInfo aInfo(aStartPos, rSpanTxt,
208 nSpanIdx, nSpanLen,
209 m_aFont, m_rInfo.mnPara,
210 aDXArray, aKashidaArray,
211 nullptr, /* no spelling in subportion, handled outside */
212 nullptr, /* no field in subportion, handled outside */
213 m_rInfo.mpLocale, m_rInfo.maOverlineColor, m_rInfo.maTextLineColor,
214 m_rInfo.mnBiDiLevel,
215 false, false, false);
217 m_rHandler.impCreateTextPortionPrimitive(aInfo);
219 if (!bUpper)
220 m_aFont.SetPropr(nProp);
224 void impTextBreakupHandler::impCreateTextPortionPrimitive(const DrawPortionInfo& rInfo)
226 if(rInfo.maText.isEmpty() || !rInfo.mnTextLen)
227 return;
229 basegfx::B2DVector aFontScaling;
230 drawinglayer::attribute::FontAttribute aFontAttribute(
231 drawinglayer::primitive2d::getFontAttributeFromVclFont(
232 aFontScaling,
233 rInfo.mrFont,
234 rInfo.IsRTL(),
235 false));
236 basegfx::B2DHomMatrix aNewTransform;
238 // add font scale to new transform
239 aNewTransform.scale(aFontScaling.getX(), aFontScaling.getY());
241 // look for proportional font scaling, if necessary, scale accordingly
242 sal_Int8 nPropr(rInfo.mrFont.GetPropr());
243 const double fPropFontFactor(nPropr / 100.0);
244 if (100 != nPropr)
245 aNewTransform.scale(fPropFontFactor, fPropFontFactor);
247 // apply font rotate
248 if(rInfo.mrFont.GetOrientation())
250 aNewTransform.rotate(-toRadians(rInfo.mrFont.GetOrientation()));
253 // look for escapement, if necessary, translate accordingly
254 if(rInfo.mrFont.GetEscapement())
256 sal_Int16 nEsc(rInfo.mrFont.GetEscapement());
258 if(DFLT_ESC_AUTO_SUPER == nEsc)
260 nEsc = .8 * (100 - nPropr);
261 assert (nEsc == DFLT_ESC_SUPER && "I'm sure this formula needs to be changed, but how to confirm that???");
262 nEsc = DFLT_ESC_SUPER;
264 else if(DFLT_ESC_AUTO_SUB == nEsc)
266 nEsc = .2 * -(100 - nPropr);
267 assert (nEsc == -20 && "I'm sure this formula needs to be changed, but how to confirm that???");
268 nEsc = -20;
271 if(nEsc > MAX_ESC_POS)
273 nEsc = MAX_ESC_POS;
275 else if(nEsc < -MAX_ESC_POS)
277 nEsc = -MAX_ESC_POS;
280 const double fEscapement(nEsc / -100.0);
281 aNewTransform.translate(0.0, fEscapement * aFontScaling.getY());
284 // apply transformA
285 aNewTransform *= maNewTransformA;
287 // apply local offset
288 aNewTransform.translate(rInfo.mrStartPos.X(), rInfo.mrStartPos.Y());
290 // also apply embedding object's transform
291 aNewTransform *= maNewTransformB;
293 // prepare DXArray content. To make it independent from font size (and such from
294 // the text transformation), scale it to unit coordinates
295 ::std::vector< double > aDXArray;
297 if (!rInfo.mpDXArray.empty())
299 aDXArray.reserve(rInfo.mnTextLen);
300 for(sal_Int32 a=0; a < rInfo.mnTextLen; a++)
302 aDXArray.push_back(rInfo.mpDXArray[a]);
306 OUString caseMappedText = rInfo.mrFont.CalcCaseMap(rInfo.maText);
307 rtl::Reference<drawinglayer::primitive2d::BasePrimitive2D> pNewPrimitive(buildTextPortionPrimitive(rInfo, caseMappedText,
308 aFontAttribute,
309 aDXArray, aNewTransform));
311 bool bSmallCaps = rInfo.mrFont.IsCapital();
312 if (bSmallCaps && rInfo.mpDXArray.empty())
314 SAL_WARN("svx", "SmallCaps requested with DXArray, abandoning");
315 bSmallCaps = false;
317 if (bSmallCaps)
319 // rerun with each sub-portion
320 DoCapitalsDrawPortionInfo aDoDrawPortionInfo(*this, rInfo);
321 rInfo.mrFont.DoOnCapitals(aDoDrawPortionInfo);
323 // transfer collected primitives from maTextPortionPrimitives to a new container
324 drawinglayer::primitive2d::Primitive2DContainer aContainer = std::move(maTextPortionPrimitives);
326 // Take any decoration for the whole formatted portion and keep it to get continuous over/under/strike-through
327 if (pNewPrimitive->getPrimitive2DID() == PRIMITIVE2D_ID_TEXTDECORATEDPORTIONPRIMITIVE2D)
329 const drawinglayer::primitive2d::TextDecoratedPortionPrimitive2D* pTCPP =
330 static_cast<const drawinglayer::primitive2d::TextDecoratedPortionPrimitive2D*>(pNewPrimitive.get());
332 if (pTCPP->getWordLineMode()) // single word mode: 'Individual words' in UI
334 // Split to single word primitives using TextBreakupHelper
335 drawinglayer::primitive2d::TextBreakupHelper aTextBreakupHelper(*pTCPP);
336 drawinglayer::primitive2d::Primitive2DContainer aBroken(aTextBreakupHelper.extractResult(drawinglayer::primitive2d::BreakupUnit::Word));
337 for (auto& rPortion : aBroken)
339 assert(rPortion->getPrimitive2DID() == PRIMITIVE2D_ID_TEXTDECORATEDPORTIONPRIMITIVE2D &&
340 "TextBreakupHelper generates same output primitive type as input");
342 const drawinglayer::primitive2d::TextDecoratedPortionPrimitive2D* pPortion =
343 static_cast<const drawinglayer::primitive2d::TextDecoratedPortionPrimitive2D*>(rPortion.get());
345 // create and add decoration
346 const drawinglayer::primitive2d::Primitive2DContainer& rDecorationGeometryContent(
347 pPortion->getOrCreateDecorationGeometryContent(
348 pPortion->getTextTransform(),
349 caseMappedText,
350 pPortion->getTextPosition(),
351 pPortion->getTextLength(),
352 pPortion->getDXArray()));
354 aContainer.insert(aContainer.end(), rDecorationGeometryContent.begin(), rDecorationGeometryContent.end());
357 else
359 // create and add decoration
360 const drawinglayer::primitive2d::Primitive2DContainer& rDecorationGeometryContent(
361 pTCPP->getOrCreateDecorationGeometryContent(
362 pTCPP->getTextTransform(),
363 caseMappedText,
364 rInfo.mnTextStart,
365 rInfo.mnTextLen,
366 aDXArray));
368 aContainer.insert(aContainer.end(), rDecorationGeometryContent.begin(), rDecorationGeometryContent.end());
372 pNewPrimitive = new drawinglayer::primitive2d::GroupPrimitive2D(std::move(aContainer));
375 const Color aFontColor(rInfo.mrFont.GetColor());
376 if (aFontColor.IsTransparent())
378 // Handle semi-transparent text for both the decorated and simple case here.
379 pNewPrimitive = new drawinglayer::primitive2d::UnifiedTransparencePrimitive2D(
380 drawinglayer::primitive2d::Primitive2DContainer{ pNewPrimitive },
381 (255 - aFontColor.GetAlpha()) / 255.0);
384 if(rInfo.mbEndOfBullet)
386 // embed in TextHierarchyBulletPrimitive2D
387 drawinglayer::primitive2d::Primitive2DContainer aNewSequence { pNewPrimitive };
388 pNewPrimitive = new drawinglayer::primitive2d::TextHierarchyBulletPrimitive2D(std::move(aNewSequence));
391 if(rInfo.mpFieldData)
393 pNewPrimitive = impCheckFieldPrimitive(pNewPrimitive.get(), rInfo);
396 maTextPortionPrimitives.push_back(pNewPrimitive);
398 // support for WrongSpellVector. Create WrongSpellPrimitives as needed
399 if(!rInfo.mpWrongSpellVector || aDXArray.empty())
400 return;
402 const sal_Int32 nSize(rInfo.mpWrongSpellVector->size());
403 const sal_Int32 nDXCount(aDXArray.size());
404 const basegfx::BColor aSpellColor(1.0, 0.0, 0.0); // red, hard coded
406 for(sal_Int32 a(0); a < nSize; a++)
408 const EEngineData::WrongSpellClass& rCandidate = (*rInfo.mpWrongSpellVector)[a];
410 if(rCandidate.nStart >= rInfo.mnTextStart && rCandidate.nEnd >= rInfo.mnTextStart && rCandidate.nEnd > rCandidate.nStart)
412 const sal_Int32 nStart(rCandidate.nStart - rInfo.mnTextStart);
413 const sal_Int32 nEnd(rCandidate.nEnd - rInfo.mnTextStart);
414 double fStart(0.0);
415 double fEnd(0.0);
417 if(nStart > 0 && nStart - 1 < nDXCount)
419 fStart = aDXArray[nStart - 1];
422 if(nEnd > 0 && nEnd - 1 < nDXCount)
424 fEnd = aDXArray[nEnd - 1];
427 if(!basegfx::fTools::equal(fStart, fEnd))
429 if(rInfo.IsRTL())
431 // #i98523#
432 // When the portion is RTL, mirror the redlining using the
433 // full portion width
434 const double fTextWidth(aDXArray[aDXArray.size() - 1]);
436 fStart = fTextWidth - fStart;
437 fEnd = fTextWidth - fEnd;
440 // need to take FontScaling out of values; it's already part of
441 // aNewTransform and would be double applied
442 const double fFontScaleX(aFontScaling.getX() * fPropFontFactor);
444 if(!basegfx::fTools::equal(fFontScaleX, 1.0)
445 && !basegfx::fTools::equalZero(fFontScaleX))
447 fStart /= fFontScaleX;
448 fEnd /= fFontScaleX;
451 maTextPortionPrimitives.push_back(new drawinglayer::primitive2d::WrongSpellPrimitive2D(
452 aNewTransform,
453 fStart,
454 fEnd,
455 aSpellColor));
461 rtl::Reference<drawinglayer::primitive2d::BasePrimitive2D> buildTextPortionPrimitive(
462 const DrawPortionInfo& rInfo, const OUString& rText,
463 const drawinglayer::attribute::FontAttribute& rFontAttribute,
464 const std::vector<double>& rDXArray,
465 const basegfx::B2DHomMatrix& rNewTransform)
467 ::std::vector< sal_Bool > aKashidaArray;
469 if(!rInfo.mpKashidaArray.empty() && rInfo.mnTextLen)
471 aKashidaArray.reserve(rInfo.mnTextLen);
473 for(sal_Int32 a=0; a < rInfo.mnTextLen; a++)
475 aKashidaArray.push_back(rInfo.mpKashidaArray[a]);
479 // create complex text primitive and append
480 const Color aFontColor(rInfo.mrFont.GetColor());
481 const basegfx::BColor aBFontColor(aFontColor.getBColor());
483 const Color aTextFillColor(rInfo.mrFont.GetFillColor());
485 // prepare wordLineMode (for underline and strikeout)
486 // NOT for bullet texts. It is set (this may be an error by itself), but needs to be suppressed to hinder e.g. '1)'
487 // to be split which would not look like the original
488 const bool bWordLineMode(rInfo.mrFont.IsWordLineMode() && !rInfo.mbEndOfBullet);
490 // prepare new primitive
491 rtl::Reference<drawinglayer::primitive2d::BasePrimitive2D> pNewPrimitive;
492 const bool bDecoratedIsNeeded(
493 LINESTYLE_NONE != rInfo.mrFont.GetOverline()
494 || LINESTYLE_NONE != rInfo.mrFont.GetUnderline()
495 || STRIKEOUT_NONE != rInfo.mrFont.GetStrikeout()
496 || FontEmphasisMark::NONE != (rInfo.mrFont.GetEmphasisMark() & FontEmphasisMark::Style)
497 || FontRelief::NONE != rInfo.mrFont.GetRelief()
498 || rInfo.mrFont.IsShadow()
499 || bWordLineMode);
501 if(bDecoratedIsNeeded)
503 // TextDecoratedPortionPrimitive2D needed, prepare some more data
504 // get overline and underline color. If it's on automatic (0xffffffff) use FontColor instead
505 const Color aUnderlineColor(rInfo.maTextLineColor);
506 const basegfx::BColor aBUnderlineColor((aUnderlineColor == COL_AUTO) ? aBFontColor : aUnderlineColor.getBColor());
507 const Color aOverlineColor(rInfo.maOverlineColor);
508 const basegfx::BColor aBOverlineColor((aOverlineColor == COL_AUTO) ? aBFontColor : aOverlineColor.getBColor());
510 // prepare overline and underline data
511 const drawinglayer::primitive2d::TextLine eFontOverline(
512 drawinglayer::primitive2d::mapFontLineStyleToTextLine(rInfo.mrFont.GetOverline()));
513 const drawinglayer::primitive2d::TextLine eFontLineStyle(
514 drawinglayer::primitive2d::mapFontLineStyleToTextLine(rInfo.mrFont.GetUnderline()));
516 // check UnderlineAbove
517 const bool bUnderlineAbove(
518 drawinglayer::primitive2d::TEXT_LINE_NONE != eFontLineStyle && rInfo.mrFont.IsUnderlineAbove());
520 // prepare strikeout data
521 const drawinglayer::primitive2d::TextStrikeout eTextStrikeout(
522 drawinglayer::primitive2d::mapFontStrikeoutToTextStrikeout(rInfo.mrFont.GetStrikeout()));
524 // prepare emphasis mark data
525 drawinglayer::primitive2d::TextEmphasisMark eTextEmphasisMark(drawinglayer::primitive2d::TEXT_FONT_EMPHASIS_MARK_NONE);
527 switch(rInfo.mrFont.GetEmphasisMark() & FontEmphasisMark::Style)
529 case FontEmphasisMark::Dot : eTextEmphasisMark = drawinglayer::primitive2d::TEXT_FONT_EMPHASIS_MARK_DOT; break;
530 case FontEmphasisMark::Circle : eTextEmphasisMark = drawinglayer::primitive2d::TEXT_FONT_EMPHASIS_MARK_CIRCLE; break;
531 case FontEmphasisMark::Disc : eTextEmphasisMark = drawinglayer::primitive2d::TEXT_FONT_EMPHASIS_MARK_DISC; break;
532 case FontEmphasisMark::Accent : eTextEmphasisMark = drawinglayer::primitive2d::TEXT_FONT_EMPHASIS_MARK_ACCENT; break;
533 default: break;
536 const bool bEmphasisMarkAbove(rInfo.mrFont.GetEmphasisMark() & FontEmphasisMark::PosAbove);
537 const bool bEmphasisMarkBelow(rInfo.mrFont.GetEmphasisMark() & FontEmphasisMark::PosBelow);
539 // prepare font relief data
540 drawinglayer::primitive2d::TextRelief eTextRelief(drawinglayer::primitive2d::TEXT_RELIEF_NONE);
542 switch(rInfo.mrFont.GetRelief())
544 case FontRelief::Embossed : eTextRelief = drawinglayer::primitive2d::TEXT_RELIEF_EMBOSSED; break;
545 case FontRelief::Engraved : eTextRelief = drawinglayer::primitive2d::TEXT_RELIEF_ENGRAVED; break;
546 default : break; // RELIEF_NONE, FontRelief_FORCE_EQUAL_SIZE
549 // prepare shadow/outline data
550 const bool bShadow(rInfo.mrFont.IsShadow());
552 // TextDecoratedPortionPrimitive2D is needed, create one
553 pNewPrimitive = new drawinglayer::primitive2d::TextDecoratedPortionPrimitive2D(
555 // attributes for TextSimplePortionPrimitive2D
556 rNewTransform,
557 rText,
558 rInfo.mnTextStart,
559 rInfo.mnTextLen,
560 std::vector(rDXArray),
561 std::move(aKashidaArray),
562 rFontAttribute,
563 rInfo.mpLocale ? *rInfo.mpLocale : css::lang::Locale(),
564 aBFontColor,
565 aTextFillColor,
567 // attributes for TextDecoratedPortionPrimitive2D
568 aBOverlineColor,
569 aBUnderlineColor,
570 eFontOverline,
571 eFontLineStyle,
572 bUnderlineAbove,
573 eTextStrikeout,
574 bWordLineMode,
575 eTextEmphasisMark,
576 bEmphasisMarkAbove,
577 bEmphasisMarkBelow,
578 eTextRelief,
579 bShadow);
581 else
583 // TextSimplePortionPrimitive2D is enough
584 pNewPrimitive = new drawinglayer::primitive2d::TextSimplePortionPrimitive2D(
585 rNewTransform,
586 rText,
587 rInfo.mnTextStart,
588 rInfo.mnTextLen,
589 std::vector(rDXArray),
590 std::move(aKashidaArray),
591 rFontAttribute,
592 rInfo.mpLocale ? *rInfo.mpLocale : css::lang::Locale(),
593 aBFontColor,
594 aTextFillColor);
597 return pNewPrimitive;
600 rtl::Reference<drawinglayer::primitive2d::BasePrimitive2D> impTextBreakupHandler::impCheckFieldPrimitive(drawinglayer::primitive2d::BasePrimitive2D* pPrimitive, const DrawPortionInfo& rInfo)
602 rtl::Reference<drawinglayer::primitive2d::BasePrimitive2D> xRet = pPrimitive;
603 if(rInfo.mpFieldData)
605 // Support for FIELD_SEQ_BEGIN, FIELD_SEQ_END. If used, create a TextHierarchyFieldPrimitive2D
606 // which holds the field type and, if applicable, the URL
607 const SvxURLField* pURLField = dynamic_cast< const SvxURLField* >(rInfo.mpFieldData);
608 const SvxPageField* pPageField = dynamic_cast< const SvxPageField* >(rInfo.mpFieldData);
610 // embed current primitive to a sequence
611 drawinglayer::primitive2d::Primitive2DContainer aSequence;
613 if(pPrimitive)
615 aSequence.resize(1);
616 aSequence[0] = drawinglayer::primitive2d::Primitive2DReference(pPrimitive);
619 if(pURLField)
621 // extended this to hold more of the contents of the original
622 // SvxURLField since that stuff is still used in HitTest and e.g. Calc
623 std::vector< std::pair< OUString, OUString>> meValues;
624 meValues.emplace_back("URL", pURLField->GetURL());
625 meValues.emplace_back("Representation", pURLField->GetRepresentation());
626 meValues.emplace_back("TargetFrame", pURLField->GetTargetFrame());
627 meValues.emplace_back("SvxURLFormat", OUString::number(static_cast<sal_uInt16>(pURLField->GetFormat())));
628 xRet = new drawinglayer::primitive2d::TextHierarchyFieldPrimitive2D(std::move(aSequence), drawinglayer::primitive2d::FIELD_TYPE_URL, &meValues);
630 else if(pPageField)
632 xRet = new drawinglayer::primitive2d::TextHierarchyFieldPrimitive2D(std::move(aSequence), drawinglayer::primitive2d::FIELD_TYPE_PAGE);
634 else
636 xRet = new drawinglayer::primitive2d::TextHierarchyFieldPrimitive2D(std::move(aSequence), drawinglayer::primitive2d::FIELD_TYPE_COMMON);
640 return xRet;
643 void impTextBreakupHandler::impFlushTextPortionPrimitivesToLinePrimitives()
645 // only create a line primitive when we had content; there is no need for
646 // empty line primitives (contrary to paragraphs, see below).
647 if(!maTextPortionPrimitives.empty())
649 maLinePrimitives.push_back(new drawinglayer::primitive2d::TextHierarchyLinePrimitive2D(std::move(maTextPortionPrimitives)));
653 void impTextBreakupHandler::impFlushLinePrimitivesToParagraphPrimitives(sal_Int32 nPara)
655 sal_Int16 nDepth = mrOutliner.GetDepth(nPara);
656 EBulletInfo eInfo = mrOutliner.GetBulletInfo(nPara);
657 // Pass -1 to signal VclMetafileProcessor2D that there is no active
658 // bullets/numbering in this paragraph (i.e. this is normal text)
659 const sal_Int16 nOutlineLevel( eInfo.bVisible ? nDepth : -1);
661 // ALWAYS create a paragraph primitive, even when no content was added. This is done to
662 // have the correct paragraph count even with empty paragraphs. Those paragraphs will
663 // have an empty sub-PrimitiveSequence.
664 maParagraphPrimitives.push_back(
665 new drawinglayer::primitive2d::TextHierarchyParagraphPrimitive2D(
666 std::move(maLinePrimitives),
667 nOutlineLevel));
670 void impTextBreakupHandler::impHandleDrawPortionInfo(const DrawPortionInfo& rInfo)
672 impCreateTextPortionPrimitive(rInfo);
674 if(rInfo.mbEndOfLine || rInfo.mbEndOfParagraph)
676 impFlushTextPortionPrimitivesToLinePrimitives();
679 if(rInfo.mbEndOfParagraph)
681 impFlushLinePrimitivesToParagraphPrimitives(rInfo.mnPara);
685 void impTextBreakupHandler::impHandleDrawBulletInfo(const DrawBulletInfo& rInfo)
687 basegfx::B2DHomMatrix aNewTransform;
689 // add size to new transform
690 aNewTransform.scale(rInfo.maBulletSize.getWidth(), rInfo.maBulletSize.getHeight());
692 // apply transformA
693 aNewTransform *= maNewTransformA;
695 // apply local offset
696 aNewTransform.translate(rInfo.maBulletPosition.X(), rInfo.maBulletPosition.Y());
698 // also apply embedding object's transform
699 aNewTransform *= maNewTransformB;
701 // prepare empty GraphicAttr
702 const GraphicAttr aGraphicAttr;
704 // create GraphicPrimitive2D
705 const drawinglayer::primitive2d::Primitive2DReference aNewReference(new drawinglayer::primitive2d::GraphicPrimitive2D(
706 aNewTransform,
707 rInfo.maBulletGraphicObject,
708 aGraphicAttr));
710 // embed in TextHierarchyBulletPrimitive2D
711 drawinglayer::primitive2d::Primitive2DContainer aNewSequence { aNewReference };
712 rtl::Reference<drawinglayer::primitive2d::BasePrimitive2D> pNewPrimitive = new drawinglayer::primitive2d::TextHierarchyBulletPrimitive2D(std::move(aNewSequence));
714 // add to output
715 maTextPortionPrimitives.push_back(pNewPrimitive);
718 IMPL_LINK(impTextBreakupHandler, decomposeContourTextPrimitive, DrawPortionInfo*, pInfo, void)
720 // for contour text, ignore (clip away) all portions which are below
721 // the visible area given by maScale
722 if(pInfo && static_cast<double>(pInfo->mrStartPos.Y()) < maScale.getY())
724 impHandleDrawPortionInfo(*pInfo);
728 IMPL_LINK(impTextBreakupHandler, decomposeBlockTextPrimitive, DrawPortionInfo*, pInfo, void)
730 if(!pInfo)
731 return;
733 // Is clipping wanted? This is text clipping; only accept a portion
734 // if it's completely in the range
735 if(!maClipRange.isEmpty())
737 // Test start position first; this allows to not get the text range at
738 // all if text is far outside
739 const basegfx::B2DPoint aStartPosition(pInfo->mrStartPos.X(), pInfo->mrStartPos.Y());
741 if(!maClipRange.isInside(aStartPosition))
743 return;
746 // Start position is inside. Get TextBoundRect and TopLeft next
747 drawinglayer::primitive2d::TextLayouterDevice aTextLayouterDevice;
748 aTextLayouterDevice.setFont(pInfo->mrFont);
750 const basegfx::B2DRange aTextBoundRect(
751 aTextLayouterDevice.getTextBoundRect(
752 pInfo->maText, pInfo->mnTextStart, pInfo->mnTextLen));
753 const basegfx::B2DPoint aTopLeft(aTextBoundRect.getMinimum() + aStartPosition);
755 if(!maClipRange.isInside(aTopLeft))
757 return;
760 // TopLeft is inside. Get BottomRight and check
761 const basegfx::B2DPoint aBottomRight(aTextBoundRect.getMaximum() + aStartPosition);
763 if(!maClipRange.isInside(aBottomRight))
765 return;
768 // all inside, clip was successful
770 impHandleDrawPortionInfo(*pInfo);
773 IMPL_LINK(impTextBreakupHandler, decomposeStretchTextPrimitive, DrawPortionInfo*, pInfo, void)
775 if(pInfo)
777 impHandleDrawPortionInfo(*pInfo);
781 IMPL_LINK(impTextBreakupHandler, decomposeContourBulletPrimitive, DrawBulletInfo*, pInfo, void)
783 if(pInfo)
785 impHandleDrawBulletInfo(*pInfo);
789 IMPL_LINK(impTextBreakupHandler, decomposeBlockBulletPrimitive, DrawBulletInfo*, pInfo, void)
791 if(pInfo)
793 impHandleDrawBulletInfo(*pInfo);
797 IMPL_LINK(impTextBreakupHandler, decomposeStretchBulletPrimitive, DrawBulletInfo*, pInfo, void)
799 if(pInfo)
801 impHandleDrawBulletInfo(*pInfo);
805 drawinglayer::primitive2d::Primitive2DContainer impTextBreakupHandler::extractPrimitive2DSequence()
807 if(!maTextPortionPrimitives.empty())
809 // collect non-closed lines
810 impFlushTextPortionPrimitivesToLinePrimitives();
813 if(!maLinePrimitives.empty())
815 // collect non-closed paragraphs
816 impFlushLinePrimitivesToParagraphPrimitives(mrOutliner.GetParagraphCount() - 1);
819 return std::move(maParagraphPrimitives);
821 } // end of anonymous namespace
824 // primitive decompositions
826 void SdrTextObj::impDecomposeContourTextPrimitive(
827 drawinglayer::primitive2d::Primitive2DContainer& rTarget,
828 const drawinglayer::primitive2d::SdrContourTextPrimitive2D& rSdrContourTextPrimitive,
829 const drawinglayer::geometry::ViewInformation2D& aViewInformation) const
831 basegfx::B2DHomMatrix aObjectMatrix = rSdrContourTextPrimitive.getObjectTransform();
832 basegfx::B2DPolyPolygon aPolyPolygon(rSdrContourTextPrimitive.getUnitPolyPolygon());
834 // decompose aObjectMatrix
835 basegfx::B2DTuple aScale, aTranslate;
836 double fRotate, fShearX;
837 aObjectMatrix.decompose(aScale, aTranslate, fRotate, fShearX);
839 // tdf#84507 The aPolyPolygon is not suitable for the text in case of rotate or shear.
840 if (!basegfx::fTools::equalZero(fRotate) || !basegfx::fTools::equalZero(fShearX))
842 // unitPolyPolygon was build by inverse(aObjectMatrix) * PolyPolygon.
843 // Restore to PolyPolygon.
844 aPolyPolygon.transform(aObjectMatrix);
846 // outliner expects an unrotated, unsheared polypolygon with top-left in origin.
847 // Remember top-left of aPolyPolygon.
848 basegfx::B2DTuple aTargetLeftTop = aPolyPolygon.getB2DRange().getMinimum();
849 // Remove rotation if any
850 basegfx::B2DHomMatrix aRemoveRotShear;
851 if (!basegfx::fTools::equalZero(fRotate))
852 aRemoveRotShear *= basegfx::utils::createRotateB2DHomMatrix(-fRotate);
853 // Remove shear if any
854 if (!basegfx::fTools::equalZero(fShearX))
855 aRemoveRotShear *= basegfx::utils::createShearXB2DHomMatrix(-fShearX);
856 aPolyPolygon.transform(aRemoveRotShear);
857 // Move Top/Left to origin
858 basegfx::B2DRange aBoundRange = aPolyPolygon.getB2DRange();
859 aPolyPolygon.transform(
860 basegfx::utils::createTranslateB2DHomMatrix(-aBoundRange.getMinimum()));
862 // Calculate the translation needed to bring the text to the original position of
863 // aPolyPolygon.
864 basegfx::B2DPolyPolygon aTemp(aPolyPolygon);
865 aTemp.transform(
866 basegfx::utils::createShearXRotateTranslateB2DHomMatrix(fShearX, fRotate, 0.0, 0.0));
867 basegfx::B2DTuple aTempLeftTop = aTemp.getB2DRange().getMinimum();
868 aTranslate = aTargetLeftTop - aTempLeftTop;
870 else
872 // scale up to original size
873 aPolyPolygon.transform(
874 basegfx::utils::createScaleB2DHomMatrix(fabs(aScale.getX()), fabs(aScale.getY())));
877 // prepare outliner
878 SolarMutexGuard aSolarGuard;
879 SdrOutliner& rOutliner = ImpGetDrawOutliner();
880 const Size aNullSize;
881 rOutliner.SetPaperSize(aNullSize);
882 rOutliner.SetPolygon(aPolyPolygon);
883 rOutliner.SetUpdateLayout(true);
884 rOutliner.SetText(rSdrContourTextPrimitive.getOutlinerParaObject());
886 // set visualizing page at Outliner; needed e.g. for PageNumberField decomposition
887 rOutliner.setVisualizedPage(GetSdrPageFromXDrawPage(aViewInformation.getVisualizedPage()));
889 // prepare matrices to apply to newly created primitives
890 basegfx::B2DHomMatrix aNewTransformA;
891 // East Asian vertical writing mode needs text start at TopRight.
892 const OutlinerParaObject& rOutlinerParaObject
893 = rSdrContourTextPrimitive.getOutlinerParaObject();
894 const bool bVerticalWriting(rOutlinerParaObject.IsEffectivelyVertical());
895 const bool bTopToBottom(rOutlinerParaObject.IsTopToBottom());
896 if (bVerticalWriting && bTopToBottom)
898 const double fStartInX = aPolyPolygon.getB2DRange().getMaximum().getX();
899 aNewTransformA *= basegfx::utils::createTranslateB2DHomMatrix(fStartInX, 0.0);
902 // mirroring. We are now in the polygon sizes. When mirroring in X and Y,
903 // move the null point which was top left to bottom right.
904 const bool bMirrorX(aScale.getX() < 0.0);
905 const bool bMirrorY(aScale.getY() < 0.0);
907 // in-between the translations of the single primitives will take place. Afterwards,
908 // the object's transformations need to be applied
909 const basegfx::B2DHomMatrix aNewTransformB(basegfx::utils::createScaleShearXRotateTranslateB2DHomMatrix(
910 bMirrorX ? -1.0 : 1.0, bMirrorY ? -1.0 : 1.0,
911 fShearX, fRotate, aTranslate.getX(), aTranslate.getY()));
913 // now break up text primitives. If it has a fat stroke, createTextPrimitive() has created a
914 // ScaledUnitPolyPolygon. Thus aPolyPolygon might be smaller than aScale from aObjectMatrix. We
915 // use this smaller size for the text area, otherwise the text will reach into the stroke.
916 impTextBreakupHandler aConverter(rOutliner);
917 aConverter.decomposeContourTextPrimitive(aNewTransformA, aNewTransformB,
918 aPolyPolygon.getB2DRange().getRange());
920 // cleanup outliner
921 rOutliner.Clear();
922 rOutliner.setVisualizedPage(nullptr);
924 rTarget = aConverter.extractPrimitive2DSequence();
927 void SdrTextObj::impDecomposeAutoFitTextPrimitive(
928 drawinglayer::primitive2d::Primitive2DContainer& rTarget,
929 const drawinglayer::primitive2d::SdrAutoFitTextPrimitive2D& rSdrAutofitTextPrimitive,
930 const drawinglayer::geometry::ViewInformation2D& aViewInformation) const
932 // decompose matrix to have position and size of text
933 basegfx::B2DVector aScale, aTranslate;
934 double fRotate, fShearX;
935 rSdrAutofitTextPrimitive.getTextRangeTransform().decompose(aScale, aTranslate, fRotate, fShearX);
937 // use B2DRange aAnchorTextRange for calculations
938 basegfx::B2DRange aAnchorTextRange(aTranslate);
939 aAnchorTextRange.expand(aTranslate + aScale);
941 // prepare outliner
942 const SfxItemSet& rTextItemSet = rSdrAutofitTextPrimitive.getSdrText()->GetItemSet();
943 SolarMutexGuard aSolarGuard;
944 SdrOutliner& rOutliner = ImpGetDrawOutliner();
945 SdrTextVertAdjust eVAdj = GetTextVerticalAdjust(rTextItemSet);
946 SdrTextHorzAdjust eHAdj = GetTextHorizontalAdjust(rTextItemSet);
947 const EEControlBits nOriginalControlWord(rOutliner.GetControlWord());
948 const Size aNullSize;
950 // set visualizing page at Outliner; needed e.g. for PageNumberField decomposition
951 rOutliner.setVisualizedPage(GetSdrPageFromXDrawPage(aViewInformation.getVisualizedPage()));
953 rOutliner.SetControlWord(nOriginalControlWord|EEControlBits::AUTOPAGESIZE|EEControlBits::STRETCHING);
954 rOutliner.SetMinAutoPaperSize(aNullSize);
955 rOutliner.SetMaxAutoPaperSize(Size(1000000,1000000));
957 // That color needs to be restored on leaving this method
958 Color aOriginalBackColor(rOutliner.GetBackgroundColor());
959 setSuitableOutlinerBg(rOutliner);
961 // add one to range sizes to get back to the old Rectangle and outliner measurements
962 const sal_uInt32 nAnchorTextWidth(basegfx::fround<sal_uInt32>(aAnchorTextRange.getWidth() + 1));
963 const sal_uInt32 nAnchorTextHeight(basegfx::fround<sal_uInt32>(aAnchorTextRange.getHeight() + 1));
964 const OutlinerParaObject* pOutlinerParaObject = rSdrAutofitTextPrimitive.getSdrText()->GetOutlinerParaObject();
965 OSL_ENSURE(pOutlinerParaObject, "impDecomposeBlockTextPrimitive used with no OutlinerParaObject (!)");
966 const bool bVerticalWriting(pOutlinerParaObject->IsEffectivelyVertical());
967 const bool bTopToBottom(pOutlinerParaObject->IsTopToBottom());
968 const Size aAnchorTextSize(Size(nAnchorTextWidth, nAnchorTextHeight));
970 if(rSdrAutofitTextPrimitive.getWordWrap() || IsTextFrame())
972 rOutliner.SetMaxAutoPaperSize(aAnchorTextSize);
975 if(SDRTEXTHORZADJUST_BLOCK == eHAdj && !bVerticalWriting)
977 rOutliner.SetMinAutoPaperSize(Size(nAnchorTextWidth, 0));
978 rOutliner.SetMinColumnWrapHeight(nAnchorTextHeight);
981 if(SDRTEXTVERTADJUST_BLOCK == eVAdj && bVerticalWriting)
983 rOutliner.SetMinAutoPaperSize(Size(0, nAnchorTextHeight));
984 rOutliner.SetMinColumnWrapHeight(nAnchorTextWidth);
987 rOutliner.SetPaperSize(aAnchorTextSize);
988 rOutliner.SetUpdateLayout(true);
989 rOutliner.SetText(*pOutlinerParaObject);
991 setupAutoFitText(rOutliner, aAnchorTextSize);
992 // set visualizing page at Outliner; needed e.g. for PageNumberField decomposition
993 rOutliner.setVisualizedPage(GetSdrPageFromXDrawPage(aViewInformation.getVisualizedPage()));
995 // Do not forget to set FixedCellHeight, else the line heights will not be correct,
996 // see impDecomposeBlockTextPrimitive or impDecomposeStretchTextPrimitive in this
997 // file. The visualization for paint and in EditMode would be different. Since
998 // SetFixedCellHeight *is* used/done in ::BegTextEdit the error is that it is *not*
999 // done here (in contrast to BlockText and StretchText)
1000 rOutliner.SetFixedCellHeight(rSdrAutofitTextPrimitive.isFixedCellHeight());
1002 // now get back the layouted text size from outliner
1003 const Size aOutlinerTextSize(rOutliner.GetPaperSize());
1004 const basegfx::B2DVector aOutlinerScale(aOutlinerTextSize.Width(), aOutlinerTextSize.Height());
1005 basegfx::B2DVector aAdjustTranslate(0.0, 0.0);
1007 // correct horizontal translation using the now known text size
1008 if(SDRTEXTHORZADJUST_CENTER == eHAdj || SDRTEXTHORZADJUST_RIGHT == eHAdj)
1010 const double fFree(aAnchorTextRange.getWidth() - aOutlinerScale.getX());
1012 if(SDRTEXTHORZADJUST_CENTER == eHAdj)
1014 aAdjustTranslate.setX(fFree / 2.0);
1017 if(SDRTEXTHORZADJUST_RIGHT == eHAdj)
1019 aAdjustTranslate.setX(fFree);
1023 // correct vertical translation using the now known text size
1024 if(SDRTEXTVERTADJUST_CENTER == eVAdj || SDRTEXTVERTADJUST_BOTTOM == eVAdj)
1026 const double fFree(aAnchorTextRange.getHeight() - aOutlinerScale.getY());
1028 if(SDRTEXTVERTADJUST_CENTER == eVAdj)
1030 aAdjustTranslate.setY(fFree / 2.0);
1033 if(SDRTEXTVERTADJUST_BOTTOM == eVAdj)
1035 aAdjustTranslate.setY(fFree);
1039 // prepare matrices to apply to newly created primitives. aNewTransformA
1040 // will get coordinates in aOutlinerScale size and positive in X, Y.
1041 basegfx::B2DHomMatrix aNewTransformA;
1042 basegfx::B2DHomMatrix aNewTransformB;
1044 // translate relative to given primitive to get same rotation and shear
1045 // as the master shape we are working on. For vertical, use the top-right
1046 // corner
1047 const double fStartInX(bVerticalWriting && bTopToBottom ? aAdjustTranslate.getX() + aOutlinerScale.getX() : aAdjustTranslate.getX());
1048 const double fStartInY(bVerticalWriting && !bTopToBottom ? aAdjustTranslate.getY() + aOutlinerScale.getY() : aAdjustTranslate.getY());
1049 aNewTransformA.translate(fStartInX, fStartInY);
1051 // mirroring. We are now in aAnchorTextRange sizes. When mirroring in X and Y,
1052 // move the null point which was top left to bottom right.
1053 const bool bMirrorX(aScale.getX() < 0.0);
1054 const bool bMirrorY(aScale.getY() < 0.0);
1055 aNewTransformB.scale(bMirrorX ? -1.0 : 1.0, bMirrorY ? -1.0 : 1.0);
1057 // in-between the translations of the single primitives will take place. Afterwards,
1058 // the object's transformations need to be applied
1059 aNewTransformB.shearX(fShearX);
1060 aNewTransformB.rotate(fRotate);
1061 aNewTransformB.translate(aTranslate.getX(), aTranslate.getY());
1063 basegfx::B2DRange aClipRange;
1065 // now break up text primitives.
1066 impTextBreakupHandler aConverter(rOutliner);
1067 aConverter.decomposeBlockTextPrimitive(aNewTransformA, aNewTransformB, aClipRange);
1069 // cleanup outliner
1070 rOutliner.SetBackgroundColor(aOriginalBackColor);
1071 rOutliner.Clear();
1072 rOutliner.setVisualizedPage(nullptr);
1073 rOutliner.SetControlWord(nOriginalControlWord);
1075 rTarget = aConverter.extractPrimitive2DSequence();
1078 // Resolves: fdo#35779 set background color of this shape as the editeng background if there
1079 // is one. Check the shape itself, then the host page, then that page's master page.
1080 bool SdrObject::setSuitableOutlinerBg(::Outliner& rOutliner) const
1082 const SfxItemSet* pBackgroundFillSet = getBackgroundFillSet();
1083 if (drawing::FillStyle_NONE != pBackgroundFillSet->Get(XATTR_FILLSTYLE).GetValue())
1085 Color aColor(GetDraftFillColor(*pBackgroundFillSet).value_or(rOutliner.GetBackgroundColor()));
1086 rOutliner.SetBackgroundColor(aColor);
1087 return true;
1089 return false;
1092 const SfxItemSet* SdrObject::getBackgroundFillSet() const
1094 const SfxItemSet* pBackgroundFillSet = &GetObjectItemSet();
1096 if (drawing::FillStyle_NONE == pBackgroundFillSet->Get(XATTR_FILLSTYLE).GetValue())
1098 SdrPage* pOwnerPage(getSdrPageFromSdrObject());
1099 if (pOwnerPage)
1101 pBackgroundFillSet = &pOwnerPage->getSdrPageProperties().GetItemSet();
1103 if (drawing::FillStyle_NONE == pBackgroundFillSet->Get(XATTR_FILLSTYLE).GetValue())
1105 if (!pOwnerPage->IsMasterPage() && pOwnerPage->TRG_HasMasterPage())
1107 pBackgroundFillSet = &pOwnerPage->TRG_GetMasterPage().getSdrPageProperties().GetItemSet();
1112 return pBackgroundFillSet;
1115 const Graphic* SdrObject::getFillGraphic() const
1117 if(IsGroupObject()) // Doesn't make sense, and GetObjectItemSet() asserts.
1118 return nullptr;
1119 const SfxItemSet* pBackgroundFillSet = getBackgroundFillSet();
1120 if (drawing::FillStyle_BITMAP != pBackgroundFillSet->Get(XATTR_FILLSTYLE).GetValue())
1121 return nullptr;
1122 return &pBackgroundFillSet->Get(XATTR_FILLBITMAP).GetGraphicObject().GetGraphic();
1125 void SdrTextObj::impDecomposeBlockTextPrimitive(
1126 drawinglayer::primitive2d::Primitive2DContainer& rTarget,
1127 const drawinglayer::primitive2d::SdrBlockTextPrimitive2D& rSdrBlockTextPrimitive,
1128 const drawinglayer::geometry::ViewInformation2D& aViewInformation) const
1130 // decompose matrix to have position and size of text
1131 basegfx::B2DVector aScale, aTranslate;
1132 double fRotate, fShearX;
1133 rSdrBlockTextPrimitive.getTextRangeTransform().decompose(aScale, aTranslate, fRotate, fShearX);
1135 // use B2DRange aAnchorTextRange for calculations
1136 basegfx::B2DRange aAnchorTextRange(aTranslate);
1137 aAnchorTextRange.expand(aTranslate + aScale);
1139 // prepare outliner
1140 const bool bIsCell(rSdrBlockTextPrimitive.getCellText());
1141 SolarMutexGuard aSolarGuard;
1142 SdrOutliner& rOutliner = ImpGetDrawOutliner();
1143 SdrTextHorzAdjust eHAdj = rSdrBlockTextPrimitive.getSdrTextHorzAdjust();
1144 SdrTextVertAdjust eVAdj = rSdrBlockTextPrimitive.getSdrTextVertAdjust();
1145 const EEControlBits nOriginalControlWord(rOutliner.GetControlWord());
1146 const Size aNullSize;
1148 // set visualizing page at Outliner; needed e.g. for PageNumberField decomposition
1149 rOutliner.setVisualizedPage(GetSdrPageFromXDrawPage(aViewInformation.getVisualizedPage()));
1150 rOutliner.SetFixedCellHeight(rSdrBlockTextPrimitive.isFixedCellHeight());
1151 rOutliner.SetControlWord(nOriginalControlWord|EEControlBits::AUTOPAGESIZE);
1152 rOutliner.SetMinAutoPaperSize(aNullSize);
1153 rOutliner.SetMaxAutoPaperSize(Size(1000000,1000000));
1155 // That color needs to be restored on leaving this method
1156 Color aOriginalBackColor(rOutliner.GetBackgroundColor());
1157 setSuitableOutlinerBg(rOutliner);
1159 // add one to range sizes to get back to the old Rectangle and outliner measurements
1160 const sal_uInt32 nAnchorTextWidth(basegfx::fround<sal_uInt32>(aAnchorTextRange.getWidth() + 1));
1161 const sal_uInt32 nAnchorTextHeight(basegfx::fround<sal_uInt32>(aAnchorTextRange.getHeight() + 1));
1162 const bool bVerticalWriting(rSdrBlockTextPrimitive.getOutlinerParaObject().IsEffectivelyVertical());
1163 const bool bTopToBottom(rSdrBlockTextPrimitive.getOutlinerParaObject().IsTopToBottom());
1164 const Size aAnchorTextSize(Size(nAnchorTextWidth, nAnchorTextHeight));
1166 if(bIsCell)
1168 // cell text is formatted neither like a text object nor like an object
1169 // text, so use a special setup here
1170 rOutliner.SetMaxAutoPaperSize(aAnchorTextSize);
1172 // #i106214# To work with an unchangeable PaperSize (CellSize in
1173 // this case) Set(Min|Max)AutoPaperSize and SetPaperSize have to be used.
1174 // #i106214# This was not completely correct; to still measure the real
1175 // text height to allow vertical adjust (and vice versa for VerticalWritintg)
1176 // only one aspect has to be set, but the other one to zero
1177 if(bVerticalWriting)
1179 // measure the horizontal text size
1180 rOutliner.SetMinAutoPaperSize(Size(0, aAnchorTextSize.Height()));
1182 else
1184 // measure the vertical text size
1185 rOutliner.SetMinAutoPaperSize(Size(aAnchorTextSize.Width(), 0));
1188 rOutliner.SetPaperSize(aAnchorTextSize);
1189 rOutliner.SetUpdateLayout(true);
1190 rOutliner.SetText(rSdrBlockTextPrimitive.getOutlinerParaObject());
1192 else
1194 // check if block text is used (only one of them can be true)
1195 const bool bHorizontalIsBlock(SDRTEXTHORZADJUST_BLOCK == eHAdj && !bVerticalWriting);
1196 const bool bVerticalIsBlock(SDRTEXTVERTADJUST_BLOCK == eVAdj && bVerticalWriting);
1198 // set minimal paper size horizontally/vertically if needed
1199 if(bHorizontalIsBlock)
1201 rOutliner.SetMinAutoPaperSize(Size(nAnchorTextWidth, 0));
1202 rOutliner.SetMinColumnWrapHeight(nAnchorTextHeight);
1204 else if(bVerticalIsBlock)
1206 rOutliner.SetMinAutoPaperSize(Size(0, nAnchorTextHeight));
1207 rOutliner.SetMinColumnWrapHeight(nAnchorTextWidth);
1210 if((rSdrBlockTextPrimitive.getWordWrap() || IsTextFrame()) && !rSdrBlockTextPrimitive.getUnlimitedPage())
1212 // #i103454# maximal paper size hor/ver needs to be limited to text
1213 // frame size. If it's block text, still allow the 'other' direction
1214 // to grow to get a correct real text size when using GetPaperSize().
1215 // When just using aAnchorTextSize as maximum, GetPaperSize()
1216 // would just return aAnchorTextSize again: this means, the wanted
1217 // 'measurement' of the real size of block text would not work
1218 Size aMaxAutoPaperSize(aAnchorTextSize);
1220 // Usual processing - always grow in one of directions
1221 bool bAllowGrowVertical = !bVerticalWriting;
1222 bool bAllowGrowHorizontal = bVerticalWriting;
1224 // Compatibility mode for tdf#99729
1225 if (getSdrModelFromSdrObject().GetCompatibilityFlag(
1226 SdrCompatibilityFlag::AnchoredTextOverflowLegacy))
1228 bAllowGrowVertical = bHorizontalIsBlock;
1229 bAllowGrowHorizontal = bVerticalIsBlock;
1232 if (bAllowGrowVertical)
1234 // allow to grow vertical for horizontal texts
1235 aMaxAutoPaperSize.setHeight(1000000);
1237 else if (bAllowGrowHorizontal)
1239 // allow to grow horizontal for vertical texts
1240 aMaxAutoPaperSize.setWidth(1000000);
1243 rOutliner.SetMaxAutoPaperSize(aMaxAutoPaperSize);
1246 rOutliner.SetPaperSize(aNullSize);
1247 rOutliner.SetUpdateLayout(true);
1248 rOutliner.SetText(rSdrBlockTextPrimitive.getOutlinerParaObject());
1251 rOutliner.SetControlWord(nOriginalControlWord);
1253 // now get back the layouted text size from outliner
1254 const Size aOutlinerTextSize(rOutliner.GetPaperSize());
1255 const basegfx::B2DVector aOutlinerScale(aOutlinerTextSize.Width(), aOutlinerTextSize.Height());
1256 basegfx::B2DVector aAdjustTranslate(0.0, 0.0);
1258 // For draw objects containing text correct hor/ver alignment if text is bigger
1259 // than the object itself. Without that correction, the text would always be
1260 // formatted to the left edge (or top edge when vertical) of the draw object.
1261 if(!IsTextFrame() && !bIsCell)
1263 if(aAnchorTextRange.getWidth() < aOutlinerScale.getX() && !bVerticalWriting)
1265 // Horizontal case here. Correct only if eHAdj == SDRTEXTHORZADJUST_BLOCK,
1266 // else the alignment is wanted.
1267 if(SDRTEXTHORZADJUST_BLOCK == eHAdj)
1269 SvxAdjust eAdjust = GetObjectItemSet().Get(EE_PARA_JUST).GetAdjust();
1270 switch(eAdjust)
1272 case SvxAdjust::Left: eHAdj = SDRTEXTHORZADJUST_LEFT; break;
1273 case SvxAdjust::Right: eHAdj = SDRTEXTHORZADJUST_RIGHT; break;
1274 case SvxAdjust::Center: eHAdj = SDRTEXTHORZADJUST_CENTER; break;
1275 default: break;
1280 if(aAnchorTextRange.getHeight() < aOutlinerScale.getY() && bVerticalWriting)
1282 // Vertical case here. Correct only if eHAdj == SDRTEXTVERTADJUST_BLOCK,
1283 // else the alignment is wanted.
1284 if(SDRTEXTVERTADJUST_BLOCK == eVAdj)
1286 eVAdj = SDRTEXTVERTADJUST_CENTER;
1291 // correct horizontal translation using the now known text size
1292 if(SDRTEXTHORZADJUST_CENTER == eHAdj || SDRTEXTHORZADJUST_RIGHT == eHAdj)
1294 const double fFree(aAnchorTextRange.getWidth() - aOutlinerScale.getX());
1296 if(SDRTEXTHORZADJUST_CENTER == eHAdj)
1298 aAdjustTranslate.setX(fFree / 2.0);
1301 if(SDRTEXTHORZADJUST_RIGHT == eHAdj)
1303 aAdjustTranslate.setX(fFree);
1307 const double fFreeVerticalSpace(aAnchorTextRange.getHeight() - aOutlinerScale.getY());
1308 bool bClipVerticalTextOverflow = fFreeVerticalSpace < 0
1309 && GetObjectItemSet().Get(SDRATTR_TEXT_CLIPVERTOVERFLOW).GetValue();
1310 // correct vertical translation using the now known text size
1311 if((SDRTEXTVERTADJUST_CENTER == eVAdj || SDRTEXTVERTADJUST_BOTTOM == eVAdj)
1312 && !bClipVerticalTextOverflow)
1314 if(SDRTEXTVERTADJUST_CENTER == eVAdj)
1316 aAdjustTranslate.setY(fFreeVerticalSpace / 2.0);
1319 if(SDRTEXTVERTADJUST_BOTTOM == eVAdj)
1321 aAdjustTranslate.setY(fFreeVerticalSpace);
1325 // prepare matrices to apply to newly created primitives. aNewTransformA
1326 // will get coordinates in aOutlinerScale size and positive in X, Y.
1327 // Translate relative to given primitive to get same rotation and shear
1328 // as the master shape we are working on. For vertical, use the top-right
1329 // corner
1330 const double fStartInX(bVerticalWriting && bTopToBottom ? aAdjustTranslate.getX() + aOutlinerScale.getX() : aAdjustTranslate.getX());
1331 const double fStartInY(bVerticalWriting && !bTopToBottom ? aAdjustTranslate.getY() + aOutlinerScale.getY() : aAdjustTranslate.getY());
1332 basegfx::B2DHomMatrix aNewTransformA(basegfx::utils::createTranslateB2DHomMatrix(fStartInX, fStartInY));
1334 // Apply the camera rotation. It have to be applied after adjustment of
1335 // the text (top, bottom, center, left, right).
1336 if(GetCameraZRotation() != 0)
1338 // First find the text rect.
1339 basegfx::B2DRange aTextRectangle(/*x1=*/aTranslate.getX() + aAdjustTranslate.getX(),
1340 /*y1=*/aTranslate.getY() + aAdjustTranslate.getY(),
1341 /*x2=*/aTranslate.getX() + aOutlinerScale.getX() - aAdjustTranslate.getX(),
1342 /*y2=*/aTranslate.getY() + aOutlinerScale.getY() - aAdjustTranslate.getY());
1344 // Rotate the text from the center point.
1345 basegfx::B2DVector aTranslateToCenter(aTextRectangle.getWidth() / 2, aTextRectangle.getHeight() / 2);
1346 aNewTransformA.translate(-aTranslateToCenter.getX(), -aTranslateToCenter.getY());
1347 aNewTransformA.rotate(basegfx::deg2rad(360.0 - GetCameraZRotation() ));
1348 aNewTransformA.translate(aTranslateToCenter.getX(), aTranslateToCenter.getY());
1351 // mirroring. We are now in aAnchorTextRange sizes. When mirroring in X and Y,
1352 // move the null point which was top left to bottom right.
1353 const bool bMirrorX(aScale.getX() < 0.0);
1354 const bool bMirrorY(aScale.getY() < 0.0);
1356 // in-between the translations of the single primitives will take place. Afterwards,
1357 // the object's transformations need to be applied
1358 const basegfx::B2DHomMatrix aNewTransformB(basegfx::utils::createScaleShearXRotateTranslateB2DHomMatrix(
1359 bMirrorX ? -1.0 : 1.0, bMirrorY ? -1.0 : 1.0,
1360 fShearX, fRotate, aTranslate.getX(), aTranslate.getY()));
1363 // create ClipRange (if needed)
1364 basegfx::B2DRange aClipRange;
1365 if(bClipVerticalTextOverflow)
1366 aClipRange = {0, 0, std::numeric_limits<double>::max(), aAnchorTextRange.getHeight()};
1368 // now break up text primitives.
1369 impTextBreakupHandler aConverter(rOutliner);
1370 aConverter.decomposeBlockTextPrimitive(aNewTransformA, aNewTransformB, aClipRange);
1372 // cleanup outliner
1373 rOutliner.SetBackgroundColor(aOriginalBackColor);
1374 rOutliner.Clear();
1375 rOutliner.setVisualizedPage(nullptr);
1377 rTarget = aConverter.extractPrimitive2DSequence();
1380 void SdrTextObj::impDecomposeStretchTextPrimitive(
1381 drawinglayer::primitive2d::Primitive2DContainer& rTarget,
1382 const drawinglayer::primitive2d::SdrStretchTextPrimitive2D& rSdrStretchTextPrimitive,
1383 const drawinglayer::geometry::ViewInformation2D& aViewInformation) const
1385 // decompose matrix to have position and size of text
1386 basegfx::B2DVector aScale, aTranslate;
1387 double fRotate, fShearX;
1388 rSdrStretchTextPrimitive.getTextRangeTransform().decompose(aScale, aTranslate, fRotate, fShearX);
1390 // prepare outliner
1391 SolarMutexGuard aSolarGuard;
1392 SdrOutliner& rOutliner = ImpGetDrawOutliner();
1393 const EEControlBits nOriginalControlWord(rOutliner.GetControlWord());
1394 const Size aNullSize;
1396 rOutliner.SetControlWord(nOriginalControlWord|EEControlBits::STRETCHING|EEControlBits::AUTOPAGESIZE);
1397 rOutliner.SetFixedCellHeight(rSdrStretchTextPrimitive.isFixedCellHeight());
1398 rOutliner.SetMinAutoPaperSize(aNullSize);
1399 rOutliner.SetMaxAutoPaperSize(Size(1000000,1000000));
1400 rOutliner.SetPaperSize(aNullSize);
1401 rOutliner.SetUpdateLayout(true);
1402 rOutliner.SetText(rSdrStretchTextPrimitive.getOutlinerParaObject());
1404 // set visualizing page at Outliner; needed e.g. for PageNumberField decomposition
1405 rOutliner.setVisualizedPage(GetSdrPageFromXDrawPage(aViewInformation.getVisualizedPage()));
1407 // now get back the laid out text size from outliner
1408 const Size aOutlinerTextSize(rOutliner.CalcTextSize());
1409 const basegfx::B2DVector aOutlinerScale(
1410 aOutlinerTextSize.Width() == tools::Long(0) ? 1.0 : aOutlinerTextSize.Width(),
1411 aOutlinerTextSize.Height() == tools::Long(0) ? 1.0 : aOutlinerTextSize.Height());
1413 // prepare matrices to apply to newly created primitives
1414 basegfx::B2DHomMatrix aNewTransformA;
1416 // #i101957# Check for vertical text. If used, aNewTransformA
1417 // needs to translate the text initially around object width to orient
1418 // it relative to the topper right instead of the topper left
1419 const bool bVertical(rSdrStretchTextPrimitive.getOutlinerParaObject().IsEffectivelyVertical());
1420 const bool bTopToBottom(rSdrStretchTextPrimitive.getOutlinerParaObject().IsTopToBottom());
1422 if(bVertical)
1424 if(bTopToBottom)
1425 aNewTransformA.translate(aScale.getX(), 0.0);
1426 else
1427 aNewTransformA.translate(0.0, aScale.getY());
1430 // calculate global char stretching scale parameters. Use non-mirrored sizes
1431 // to layout without mirroring
1432 const double fScaleX(fabs(aScale.getX()) / aOutlinerScale.getX());
1433 const double fScaleY(fabs(aScale.getY()) / aOutlinerScale.getY());
1434 ScalingParameters aScalingParameters{fScaleX, fScaleY};
1436 rOutliner.setScalingParameters(aScalingParameters);
1438 // When mirroring in X and Y,
1439 // move the null point which was top left to bottom right.
1440 const bool bMirrorX(aScale.getX() < 0.0);
1441 const bool bMirrorY(aScale.getY() < 0.0);
1443 // in-between the translations of the single primitives will take place. Afterwards,
1444 // the object's transformations need to be applied
1445 const basegfx::B2DHomMatrix aNewTransformB(basegfx::utils::createScaleShearXRotateTranslateB2DHomMatrix(
1446 bMirrorX ? -1.0 : 1.0, bMirrorY ? -1.0 : 1.0,
1447 fShearX, fRotate, aTranslate.getX(), aTranslate.getY()));
1449 // now break up text primitives.
1450 impTextBreakupHandler aConverter(rOutliner);
1451 aConverter.decomposeStretchTextPrimitive(aNewTransformA, aNewTransformB);
1453 // cleanup outliner
1454 rOutliner.SetControlWord(nOriginalControlWord);
1455 rOutliner.Clear();
1456 rOutliner.setVisualizedPage(nullptr);
1458 rTarget = aConverter.extractPrimitive2DSequence();
1462 // timing generators
1463 #define ENDLESS_LOOP (0xffffffff)
1464 #define ENDLESS_TIME (double(0xffffffff))
1466 void SdrTextObj::impGetBlinkTextTiming(drawinglayer::animation::AnimationEntryList& rAnimList) const
1468 if(SdrTextAniKind::Blink != GetTextAniKind())
1469 return;
1471 // get values
1472 const SfxItemSet& rSet = GetObjectItemSet();
1473 const sal_uInt32 nRepeat(static_cast<sal_uInt32>(rSet.Get(SDRATTR_TEXT_ANICOUNT).GetValue()));
1474 double fDelay(static_cast<double>(rSet.Get(SDRATTR_TEXT_ANIDELAY).GetValue()));
1476 if(0.0 == fDelay)
1478 // use default
1479 fDelay = 250.0;
1482 // prepare loop and add
1483 drawinglayer::animation::AnimationEntryLoop aLoop(nRepeat ? nRepeat : ENDLESS_LOOP);
1484 drawinglayer::animation::AnimationEntryFixed aStart(fDelay, 0.0);
1485 aLoop.append(aStart);
1486 drawinglayer::animation::AnimationEntryFixed aEnd(fDelay, 1.0);
1487 aLoop.append(aEnd);
1488 rAnimList.append(aLoop);
1490 // add stopped state if loop is not endless
1491 if(0 != nRepeat)
1493 bool bVisibleWhenStopped(rSet.Get(SDRATTR_TEXT_ANISTOPINSIDE).GetValue());
1494 drawinglayer::animation::AnimationEntryFixed aStop(ENDLESS_TIME, bVisibleWhenStopped ? 0.0 : 1.0);
1495 rAnimList.append(aStop);
1499 static void impCreateScrollTiming(const SfxItemSet& rSet, drawinglayer::animation::AnimationEntryList& rAnimList, bool bForward, double fTimeFullPath, double fFrequency)
1501 bool bVisibleWhenStopped(rSet.Get(SDRATTR_TEXT_ANISTOPINSIDE).GetValue());
1502 bool bVisibleWhenStarted(rSet.Get(SDRATTR_TEXT_ANISTARTINSIDE).GetValue());
1503 const sal_uInt32 nRepeat(rSet.Get(SDRATTR_TEXT_ANICOUNT).GetValue());
1505 if(bVisibleWhenStarted)
1507 // move from center to outside
1508 drawinglayer::animation::AnimationEntryLinear aInOut(fTimeFullPath * 0.5, fFrequency, 0.5, bForward ? 1.0 : 0.0);
1509 rAnimList.append(aInOut);
1512 // loop. In loop, move through
1513 drawinglayer::animation::AnimationEntryLoop aLoop(nRepeat ? nRepeat : ENDLESS_LOOP);
1514 drawinglayer::animation::AnimationEntryLinear aThrough(fTimeFullPath, fFrequency, bForward ? 0.0 : 1.0, bForward ? 1.0 : 0.0);
1515 aLoop.append(aThrough);
1516 rAnimList.append(aLoop);
1518 if(0 != nRepeat && bVisibleWhenStopped)
1520 // move from outside to center
1521 drawinglayer::animation::AnimationEntryLinear aOutIn(fTimeFullPath * 0.5, fFrequency, bForward ? 0.0 : 1.0, 0.5);
1522 rAnimList.append(aOutIn);
1524 // add timing for staying at the end
1525 drawinglayer::animation::AnimationEntryFixed aEnd(ENDLESS_TIME, 0.5);
1526 rAnimList.append(aEnd);
1530 static void impCreateAlternateTiming(const SfxItemSet& rSet, drawinglayer::animation::AnimationEntryList& rAnimList, double fRelativeTextLength, bool bForward, double fTimeFullPath, double fFrequency)
1532 if(basegfx::fTools::more(fRelativeTextLength, 0.5))
1534 // this is the case when fTextLength > fFrameLength, text is bigger than animation frame.
1535 // In that case, correct direction
1536 bForward = !bForward;
1539 const double fStartPosition(bForward ? fRelativeTextLength : 1.0 - fRelativeTextLength);
1540 const double fEndPosition(bForward ? 1.0 - fRelativeTextLength : fRelativeTextLength);
1541 bool bVisibleWhenStarted(rSet.Get(SDRATTR_TEXT_ANISTARTINSIDE).GetValue());
1542 const sal_uInt32 nRepeat(rSet.Get(SDRATTR_TEXT_ANICOUNT).GetValue());
1544 if(!bVisibleWhenStarted)
1546 // move from outside to center
1547 drawinglayer::animation::AnimationEntryLinear aOutIn(fTimeFullPath * 0.5, fFrequency, bForward ? 0.0 : 1.0, 0.5);
1548 rAnimList.append(aOutIn);
1551 // loop. In loop, move out and in again. fInnerMovePath may be negative when text is bigger then frame,
1552 // so use absolute value
1553 const double fInnerMovePath(fabs(1.0 - (fRelativeTextLength * 2.0)));
1554 const double fTimeForInnerPath(fTimeFullPath * fInnerMovePath);
1555 const double fHalfInnerPath(fTimeForInnerPath * 0.5);
1556 const sal_uInt32 nDoubleRepeat(nRepeat / 2L);
1558 if(nDoubleRepeat || 0 == nRepeat)
1560 // double forth and back loop
1561 drawinglayer::animation::AnimationEntryLoop aLoop(nDoubleRepeat ? nDoubleRepeat : ENDLESS_LOOP);
1562 drawinglayer::animation::AnimationEntryLinear aTime0(fHalfInnerPath, fFrequency, 0.5, fEndPosition);
1563 aLoop.append(aTime0);
1564 drawinglayer::animation::AnimationEntryLinear aTime1(fTimeForInnerPath, fFrequency, fEndPosition, fStartPosition);
1565 aLoop.append(aTime1);
1566 drawinglayer::animation::AnimationEntryLinear aTime2(fHalfInnerPath, fFrequency, fStartPosition, 0.5);
1567 aLoop.append(aTime2);
1568 rAnimList.append(aLoop);
1571 if(nRepeat % 2L)
1573 // repeat is uneven, so we need one more forth and back to center
1574 drawinglayer::animation::AnimationEntryLinear aTime0(fHalfInnerPath, fFrequency, 0.5, fEndPosition);
1575 rAnimList.append(aTime0);
1576 drawinglayer::animation::AnimationEntryLinear aTime1(fHalfInnerPath, fFrequency, fEndPosition, 0.5);
1577 rAnimList.append(aTime1);
1580 if(0 == nRepeat)
1581 return;
1583 bool bVisibleWhenStopped(rSet.Get(SDRATTR_TEXT_ANISTOPINSIDE).GetValue());
1584 if(bVisibleWhenStopped)
1586 // add timing for staying at the end
1587 drawinglayer::animation::AnimationEntryFixed aEnd(ENDLESS_TIME, 0.5);
1588 rAnimList.append(aEnd);
1590 else
1592 // move from center to outside
1593 drawinglayer::animation::AnimationEntryLinear aInOut(fTimeFullPath * 0.5, fFrequency, 0.5, bForward ? 1.0 : 0.0);
1594 rAnimList.append(aInOut);
1598 static void impCreateSlideTiming(const SfxItemSet& rSet, drawinglayer::animation::AnimationEntryList& rAnimList, bool bForward, double fTimeFullPath, double fFrequency)
1600 // move in from outside, start outside
1601 const double fStartPosition(bForward ? 0.0 : 1.0);
1602 const sal_uInt32 nRepeat(rSet.Get(SDRATTR_TEXT_ANICOUNT).GetValue());
1604 // move from outside to center
1605 drawinglayer::animation::AnimationEntryLinear aOutIn(fTimeFullPath * 0.5, fFrequency, fStartPosition, 0.5);
1606 rAnimList.append(aOutIn);
1608 // loop. In loop, move out and in again
1609 if(nRepeat > 1 || 0 == nRepeat)
1611 drawinglayer::animation::AnimationEntryLoop aLoop(nRepeat ? nRepeat - 1 : ENDLESS_LOOP);
1612 drawinglayer::animation::AnimationEntryLinear aTime0(fTimeFullPath * 0.5, fFrequency, 0.5, fStartPosition);
1613 aLoop.append(aTime0);
1614 drawinglayer::animation::AnimationEntryLinear aTime1(fTimeFullPath * 0.5, fFrequency, fStartPosition, 0.5);
1615 aLoop.append(aTime1);
1616 rAnimList.append(aLoop);
1619 // always visible when stopped, so add timing for staying at the end when not endless
1620 if(0 != nRepeat)
1622 drawinglayer::animation::AnimationEntryFixed aEnd(ENDLESS_TIME, 0.5);
1623 rAnimList.append(aEnd);
1627 void SdrTextObj::impGetScrollTextTiming(drawinglayer::animation::AnimationEntryList& rAnimList, double fFrameLength, double fTextLength) const
1629 const SdrTextAniKind eAniKind(GetTextAniKind());
1631 if(SdrTextAniKind::Scroll != eAniKind && SdrTextAniKind::Alternate != eAniKind && SdrTextAniKind::Slide != eAniKind)
1632 return;
1634 // get data. Goal is to calculate fTimeFullPath which is the time needed to
1635 // move animation from (0.0) to (1.0) state
1636 const SfxItemSet& rSet = GetObjectItemSet();
1637 double fAnimationDelay(static_cast<double>(rSet.Get(SDRATTR_TEXT_ANIDELAY).GetValue()));
1638 double fSingleStepWidth(static_cast<double>(rSet.Get(SDRATTR_TEXT_ANIAMOUNT).GetValue()));
1639 const SdrTextAniDirection eDirection(GetTextAniDirection());
1640 const bool bForward(SdrTextAniDirection::Right == eDirection || SdrTextAniDirection::Down == eDirection);
1642 if(basegfx::fTools::equalZero(fAnimationDelay))
1644 // default to 1/20 second
1645 fAnimationDelay = 50.0;
1648 if (fSingleStepWidth < 0.0)
1650 // data is in pixels, convert to logic. Imply 96 dpi.
1651 // It makes no sense to keep the view-transformation centered
1652 // definitions, so get rid of them here.
1653 fSingleStepWidth = o3tl::convert(-fSingleStepWidth, o3tl::Length::px, o3tl::Length::mm100);
1656 if(basegfx::fTools::equalZero(fSingleStepWidth))
1658 // default to 1 millimeter
1659 fSingleStepWidth = 100.0;
1662 // use the length of the full animation path and the number of steps
1663 // to get the full path time
1664 const double fFullPathLength(fFrameLength + fTextLength);
1665 const double fNumberOfSteps(fFullPathLength / fSingleStepWidth);
1666 double fTimeFullPath(fNumberOfSteps * fAnimationDelay);
1668 if(fTimeFullPath < fAnimationDelay)
1670 fTimeFullPath = fAnimationDelay;
1673 switch(eAniKind)
1675 case SdrTextAniKind::Scroll :
1677 impCreateScrollTiming(rSet, rAnimList, bForward, fTimeFullPath, fAnimationDelay);
1678 break;
1680 case SdrTextAniKind::Alternate :
1682 double fRelativeTextLength(fTextLength / (fFrameLength + fTextLength));
1683 impCreateAlternateTiming(rSet, rAnimList, fRelativeTextLength, bForward, fTimeFullPath, fAnimationDelay);
1684 break;
1686 case SdrTextAniKind::Slide :
1688 impCreateSlideTiming(rSet, rAnimList, bForward, fTimeFullPath, fAnimationDelay);
1689 break;
1691 default : break; // SdrTextAniKind::NONE, SdrTextAniKind::Blink
1695 void SdrTextObj::impHandleChainingEventsDuringDecomposition(SdrOutliner &rOutliner) const
1697 if (GetTextChain()->GetNilChainingEvent(this))
1698 return;
1700 GetTextChain()->SetNilChainingEvent(this, true);
1702 TextChainFlow aTxtChainFlow(const_cast<SdrTextObj*>(this));
1703 bool bIsOverflow;
1705 #ifdef DBG_UTIL
1706 // Some debug output
1707 size_t nObjCount(getSdrPageFromSdrObject()->GetObjCount());
1708 for (size_t i = 0; i < nObjCount; i++)
1710 SdrTextObj* pCurObj(DynCastSdrTextObj(getSdrPageFromSdrObject()->GetObj(i)));
1711 if(pCurObj == this)
1713 SAL_INFO("svx.chaining", "Working on TextBox " << i);
1714 break;
1717 #endif
1719 aTxtChainFlow.CheckForFlowEvents(&rOutliner);
1721 if (aTxtChainFlow.IsUnderflow() && !IsInEditMode())
1723 // underflow-induced overflow
1724 aTxtChainFlow.ExecuteUnderflow(&rOutliner);
1725 bIsOverflow = aTxtChainFlow.IsOverflow();
1726 } else {
1727 // standard overflow (no underflow before)
1728 bIsOverflow = aTxtChainFlow.IsOverflow();
1731 if (bIsOverflow && !IsInEditMode()) {
1732 // Initialize Chaining Outliner
1733 SdrOutliner &rChainingOutl(getSdrModelFromSdrObject().GetChainingOutliner(this));
1734 ImpInitDrawOutliner( rChainingOutl );
1735 rChainingOutl.SetUpdateLayout(true);
1736 // We must pass the chaining outliner otherwise we would mess up decomposition
1737 aTxtChainFlow.ExecuteOverflow(&rOutliner, &rChainingOutl);
1740 GetTextChain()->SetNilChainingEvent(this, false);
1743 void SdrTextObj::impDecomposeChainedTextPrimitive(
1744 drawinglayer::primitive2d::Primitive2DContainer& rTarget,
1745 const drawinglayer::primitive2d::SdrChainedTextPrimitive2D& rSdrChainedTextPrimitive,
1746 const drawinglayer::geometry::ViewInformation2D& aViewInformation) const
1748 // decompose matrix to have position and size of text
1749 basegfx::B2DVector aScale, aTranslate;
1750 double fRotate, fShearX;
1751 rSdrChainedTextPrimitive.getTextRangeTransform().decompose(aScale, aTranslate, fRotate, fShearX);
1753 // use B2DRange aAnchorTextRange for calculations
1754 basegfx::B2DRange aAnchorTextRange(aTranslate);
1755 aAnchorTextRange.expand(aTranslate + aScale);
1757 // prepare outliner
1758 const SfxItemSet& rTextItemSet = rSdrChainedTextPrimitive.getSdrText()->GetItemSet();
1759 SolarMutexGuard aSolarGuard;
1760 SdrOutliner& rOutliner = ImpGetDrawOutliner();
1762 SdrTextVertAdjust eVAdj = GetTextVerticalAdjust(rTextItemSet);
1763 SdrTextHorzAdjust eHAdj = GetTextHorizontalAdjust(rTextItemSet);
1764 const EEControlBits nOriginalControlWord(rOutliner.GetControlWord());
1765 const Size aNullSize;
1767 // set visualizing page at Outliner; needed e.g. for PageNumberField decomposition
1768 rOutliner.setVisualizedPage(GetSdrPageFromXDrawPage(aViewInformation.getVisualizedPage()));
1770 rOutliner.SetControlWord(nOriginalControlWord|EEControlBits::AUTOPAGESIZE|EEControlBits::STRETCHING);
1771 rOutliner.SetMinAutoPaperSize(aNullSize);
1772 rOutliner.SetMaxAutoPaperSize(Size(1000000,1000000));
1774 // add one to range sizes to get back to the old Rectangle and outliner measurements
1775 const sal_uInt32 nAnchorTextWidth(basegfx::fround<sal_uInt32>(aAnchorTextRange.getWidth() + 1));
1776 const sal_uInt32 nAnchorTextHeight(basegfx::fround<sal_uInt32>(aAnchorTextRange.getHeight() + 1));
1778 // Text
1779 const OutlinerParaObject* pOutlinerParaObject = rSdrChainedTextPrimitive.getSdrText()->GetOutlinerParaObject();
1780 OSL_ENSURE(pOutlinerParaObject, "impDecomposeBlockTextPrimitive used with no OutlinerParaObject (!)");
1782 const bool bVerticalWriting(pOutlinerParaObject->IsEffectivelyVertical());
1783 const bool bTopToBottom(pOutlinerParaObject->IsTopToBottom());
1784 const Size aAnchorTextSize(Size(nAnchorTextWidth, nAnchorTextHeight));
1786 if(IsTextFrame())
1788 rOutliner.SetMaxAutoPaperSize(aAnchorTextSize);
1791 if(SDRTEXTHORZADJUST_BLOCK == eHAdj && !bVerticalWriting)
1793 rOutliner.SetMinAutoPaperSize(Size(nAnchorTextWidth, 0));
1796 if(SDRTEXTVERTADJUST_BLOCK == eVAdj && bVerticalWriting)
1798 rOutliner.SetMinAutoPaperSize(Size(0, nAnchorTextHeight));
1801 rOutliner.SetPaperSize(aNullSize);
1802 rOutliner.SetUpdateLayout(true);
1803 // Sets original text
1804 rOutliner.SetText(*pOutlinerParaObject);
1806 /* Begin overflow/underflow handling */
1808 impHandleChainingEventsDuringDecomposition(rOutliner);
1810 /* End overflow/underflow handling */
1812 // set visualizing page at Outliner; needed e.g. for PageNumberField decomposition
1813 rOutliner.setVisualizedPage(GetSdrPageFromXDrawPage(aViewInformation.getVisualizedPage()));
1815 // now get back the layouted text size from outliner
1816 const Size aOutlinerTextSize(rOutliner.GetPaperSize());
1817 const basegfx::B2DVector aOutlinerScale(aOutlinerTextSize.Width(), aOutlinerTextSize.Height());
1818 basegfx::B2DVector aAdjustTranslate(0.0, 0.0);
1820 // correct horizontal translation using the now known text size
1821 if(SDRTEXTHORZADJUST_CENTER == eHAdj || SDRTEXTHORZADJUST_RIGHT == eHAdj)
1823 const double fFree(aAnchorTextRange.getWidth() - aOutlinerScale.getX());
1825 if(SDRTEXTHORZADJUST_CENTER == eHAdj)
1827 aAdjustTranslate.setX(fFree / 2.0);
1830 if(SDRTEXTHORZADJUST_RIGHT == eHAdj)
1832 aAdjustTranslate.setX(fFree);
1836 // correct vertical translation using the now known text size
1837 if(SDRTEXTVERTADJUST_CENTER == eVAdj || SDRTEXTVERTADJUST_BOTTOM == eVAdj)
1839 const double fFree(aAnchorTextRange.getHeight() - aOutlinerScale.getY());
1841 if(SDRTEXTVERTADJUST_CENTER == eVAdj)
1843 aAdjustTranslate.setY(fFree / 2.0);
1846 if(SDRTEXTVERTADJUST_BOTTOM == eVAdj)
1848 aAdjustTranslate.setY(fFree);
1852 // prepare matrices to apply to newly created primitives. aNewTransformA
1853 // will get coordinates in aOutlinerScale size and positive in X, Y.
1854 basegfx::B2DHomMatrix aNewTransformA;
1855 basegfx::B2DHomMatrix aNewTransformB;
1857 // translate relative to given primitive to get same rotation and shear
1858 // as the master shape we are working on. For vertical, use the top-right
1859 // corner
1860 const double fStartInX(bVerticalWriting && bTopToBottom ? aAdjustTranslate.getX() + aOutlinerScale.getX() : aAdjustTranslate.getX());
1861 const double fStartInY(bVerticalWriting && !bTopToBottom ? aAdjustTranslate.getY() + aOutlinerScale.getY() : aAdjustTranslate.getY());
1862 aNewTransformA.translate(fStartInX, fStartInY);
1864 // mirroring. We are now in aAnchorTextRange sizes. When mirroring in X and Y,
1865 // move the null point which was top left to bottom right.
1866 const bool bMirrorX(aScale.getX() < 0.0);
1867 const bool bMirrorY(aScale.getY() < 0.0);
1868 aNewTransformB.scale(bMirrorX ? -1.0 : 1.0, bMirrorY ? -1.0 : 1.0);
1870 // in-between the translations of the single primitives will take place. Afterwards,
1871 // the object's transformations need to be applied
1872 aNewTransformB.shearX(fShearX);
1873 aNewTransformB.rotate(fRotate);
1874 aNewTransformB.translate(aTranslate.getX(), aTranslate.getY());
1876 basegfx::B2DRange aClipRange;
1878 // now break up text primitives.
1879 impTextBreakupHandler aConverter(rOutliner);
1880 aConverter.decomposeBlockTextPrimitive(aNewTransformA, aNewTransformB, aClipRange);
1882 // cleanup outliner
1883 rOutliner.Clear();
1884 rOutliner.setVisualizedPage(nullptr);
1885 rOutliner.SetControlWord(nOriginalControlWord);
1887 rTarget = aConverter.extractPrimitive2DSequence();
1890 // Direct decomposer for text visualization when you already have a prepared
1891 // Outliner containing all the needed information
1892 void SdrTextObj::impDecomposeBlockTextPrimitiveDirect(
1893 drawinglayer::primitive2d::Primitive2DContainer& rTarget,
1894 SdrOutliner& rOutliner,
1895 const basegfx::B2DHomMatrix& rNewTransformA,
1896 const basegfx::B2DHomMatrix& rNewTransformB,
1897 const basegfx::B2DRange& rClipRange)
1899 impTextBreakupHandler aConverter(rOutliner);
1900 aConverter.decomposeBlockTextPrimitive(rNewTransformA, rNewTransformB, rClipRange);
1901 rTarget.append(aConverter.extractPrimitive2DSequence());
1904 double SdrTextObj::GetCameraZRotation() const
1906 const css::uno::Any* pAny;
1907 double fTextCameraZRotateAngle = 0.0;
1908 const SfxItemSet& rSet = GetObjectItemSet();
1909 const SdrCustomShapeGeometryItem& rGeometryItem(rSet.Get(SDRATTR_CUSTOMSHAPE_GEOMETRY));
1911 pAny = rGeometryItem.GetPropertyValueByName(u"TextCameraZRotateAngle"_ustr);
1913 if ( pAny )
1914 *pAny >>= fTextCameraZRotateAngle;
1916 return fTextCameraZRotateAngle;
1919 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */