1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
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
;
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
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
);
112 explicit impTextBreakupHandler(SdrOutliner
& rOutliner
)
113 : mrOutliner(rOutliner
)
117 void decomposeContourTextPrimitive(const basegfx::B2DHomMatrix
& rNewTransformA
, const basegfx::B2DHomMatrix
& rNewTransformB
, const basegfx::B2DVector
& 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
163 impTextBreakupHandler
& m_rHandler
;
164 const DrawPortionInfo
& m_rInfo
;
167 DoCapitalsDrawPortionInfo(impTextBreakupHandler
& rHandler
, const DrawPortionInfo
& rInfo
)
168 : SvxDoCapitals(rInfo
.maText
, rInfo
.mnTextStart
, rInfo
.mnTextLen
)
169 , m_rHandler(rHandler
)
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
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());
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
,
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
,
215 false, false, false);
217 m_rHandler
.impCreateTextPortionPrimitive(aInfo
);
220 m_aFont
.SetPropr(nProp
);
224 void impTextBreakupHandler::impCreateTextPortionPrimitive(const DrawPortionInfo
& rInfo
)
226 if(rInfo
.maText
.isEmpty() || !rInfo
.mnTextLen
)
229 basegfx::B2DVector aFontScaling
;
230 drawinglayer::attribute::FontAttribute
aFontAttribute(
231 drawinglayer::primitive2d::getFontAttributeFromVclFont(
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);
245 aNewTransform
.scale(fPropFontFactor
, fPropFontFactor
);
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???");
271 if(nEsc
> MAX_ESC_POS
)
275 else if(nEsc
< -MAX_ESC_POS
)
280 const double fEscapement(nEsc
/ -100.0);
281 aNewTransform
.translate(0.0, fEscapement
* aFontScaling
.getY());
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
,
309 aDXArray
, aNewTransform
));
311 bool bSmallCaps
= rInfo
.mrFont
.IsCapital();
312 if (bSmallCaps
&& rInfo
.mpDXArray
.empty())
314 SAL_WARN("svx", "SmallCaps requested with DXArray, abandoning");
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(),
350 pPortion
->getTextPosition(),
351 pPortion
->getTextLength(),
352 pPortion
->getDXArray()));
354 aContainer
.insert(aContainer
.end(), rDecorationGeometryContent
.begin(), rDecorationGeometryContent
.end());
359 // create and add decoration
360 const drawinglayer::primitive2d::Primitive2DContainer
& rDecorationGeometryContent(
361 pTCPP
->getOrCreateDecorationGeometryContent(
362 pTCPP
->getTextTransform(),
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())
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
);
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
))
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
;
451 maTextPortionPrimitives
.push_back(new drawinglayer::primitive2d::WrongSpellPrimitive2D(
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()
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;
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
560 std::vector(rDXArray
),
561 std::move(aKashidaArray
),
563 rInfo
.mpLocale
? *rInfo
.mpLocale
: css::lang::Locale(),
567 // attributes for TextDecoratedPortionPrimitive2D
583 // TextSimplePortionPrimitive2D is enough
584 pNewPrimitive
= new drawinglayer::primitive2d::TextSimplePortionPrimitive2D(
589 std::vector(rDXArray
),
590 std::move(aKashidaArray
),
592 rInfo
.mpLocale
? *rInfo
.mpLocale
: css::lang::Locale(),
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
;
616 aSequence
[0] = drawinglayer::primitive2d::Primitive2DReference(pPrimitive
);
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
);
632 xRet
= new drawinglayer::primitive2d::TextHierarchyFieldPrimitive2D(std::move(aSequence
), drawinglayer::primitive2d::FIELD_TYPE_PAGE
);
636 xRet
= new drawinglayer::primitive2d::TextHierarchyFieldPrimitive2D(std::move(aSequence
), drawinglayer::primitive2d::FIELD_TYPE_COMMON
);
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
),
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());
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(
707 rInfo
.maBulletGraphicObject
,
710 // embed in TextHierarchyBulletPrimitive2D
711 drawinglayer::primitive2d::Primitive2DContainer aNewSequence
{ aNewReference
};
712 rtl::Reference
<drawinglayer::primitive2d::BasePrimitive2D
> pNewPrimitive
= new drawinglayer::primitive2d::TextHierarchyBulletPrimitive2D(std::move(aNewSequence
));
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)
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
))
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
))
760 // TopLeft is inside. Get BottomRight and check
761 const basegfx::B2DPoint
aBottomRight(aTextBoundRect
.getMaximum() + aStartPosition
);
763 if(!maClipRange
.isInside(aBottomRight
))
768 // all inside, clip was successful
770 impHandleDrawPortionInfo(*pInfo
);
773 IMPL_LINK(impTextBreakupHandler
, decomposeStretchTextPrimitive
, DrawPortionInfo
*, pInfo
, void)
777 impHandleDrawPortionInfo(*pInfo
);
781 IMPL_LINK(impTextBreakupHandler
, decomposeContourBulletPrimitive
, DrawBulletInfo
*, pInfo
, void)
785 impHandleDrawBulletInfo(*pInfo
);
789 IMPL_LINK(impTextBreakupHandler
, decomposeBlockBulletPrimitive
, DrawBulletInfo
*, pInfo
, void)
793 impHandleDrawBulletInfo(*pInfo
);
797 IMPL_LINK(impTextBreakupHandler
, decomposeStretchBulletPrimitive
, DrawBulletInfo
*, pInfo
, void)
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
864 basegfx::B2DPolyPolygon
aTemp(aPolyPolygon
);
866 basegfx::utils::createShearXRotateTranslateB2DHomMatrix(fShearX
, fRotate
, 0.0, 0.0));
867 basegfx::B2DTuple aTempLeftTop
= aTemp
.getB2DRange().getMinimum();
868 aTranslate
= aTargetLeftTop
- aTempLeftTop
;
872 // scale up to original size
873 aPolyPolygon
.transform(
874 basegfx::utils::createScaleB2DHomMatrix(fabs(aScale
.getX()), fabs(aScale
.getY())));
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());
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
);
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
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
);
1070 rOutliner
.SetBackgroundColor(aOriginalBackColor
);
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
);
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());
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.
1119 const SfxItemSet
* pBackgroundFillSet
= getBackgroundFillSet();
1120 if (drawing::FillStyle_BITMAP
!= pBackgroundFillSet
->Get(XATTR_FILLSTYLE
).GetValue())
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
);
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
));
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()));
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());
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();
1272 case SvxAdjust::Left
: eHAdj
= SDRTEXTHORZADJUST_LEFT
; break;
1273 case SvxAdjust::Right
: eHAdj
= SDRTEXTHORZADJUST_RIGHT
; break;
1274 case SvxAdjust::Center
: eHAdj
= SDRTEXTHORZADJUST_CENTER
; 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
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
);
1373 rOutliner
.SetBackgroundColor(aOriginalBackColor
);
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
);
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());
1425 aNewTransformA
.translate(aScale
.getX(), 0.0);
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
);
1454 rOutliner
.SetControlWord(nOriginalControlWord
);
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())
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()));
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);
1488 rAnimList
.append(aLoop
);
1490 // add stopped state if loop is not endless
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
);
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
);
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
);
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
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
)
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
;
1675 case SdrTextAniKind::Scroll
:
1677 impCreateScrollTiming(rSet
, rAnimList
, bForward
, fTimeFullPath
, fAnimationDelay
);
1680 case SdrTextAniKind::Alternate
:
1682 double fRelativeTextLength(fTextLength
/ (fFrameLength
+ fTextLength
));
1683 impCreateAlternateTiming(rSet
, rAnimList
, fRelativeTextLength
, bForward
, fTimeFullPath
, fAnimationDelay
);
1686 case SdrTextAniKind::Slide
:
1688 impCreateSlideTiming(rSet
, rAnimList
, bForward
, fTimeFullPath
, fAnimationDelay
);
1691 default : break; // SdrTextAniKind::NONE, SdrTextAniKind::Blink
1695 void SdrTextObj::impHandleChainingEventsDuringDecomposition(SdrOutliner
&rOutliner
) const
1697 if (GetTextChain()->GetNilChainingEvent(this))
1700 GetTextChain()->SetNilChainingEvent(this, true);
1702 TextChainFlow
aTxtChainFlow(const_cast<SdrTextObj
*>(this));
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
)));
1713 SAL_INFO("svx.chaining", "Working on TextBox " << i
);
1719 aTxtChainFlow
.CheckForFlowEvents(&rOutliner
);
1721 if (aTxtChainFlow
.IsUnderflow() && !IsInEditMode())
1723 // underflow-induced overflow
1724 aTxtChainFlow
.ExecuteUnderflow(&rOutliner
);
1725 bIsOverflow
= aTxtChainFlow
.IsOverflow();
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
);
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));
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
));
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
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
);
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
);
1914 *pAny
>>= fTextCameraZRotateAngle
;
1916 return fTextCameraZRotateAngle
;
1919 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */