1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 /*
3 * This file is part of the LibreOffice project.
4 *
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/.
8 *
9 * This file incorporates work covered by the following license notice:
10 *
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 .
18 */
19
20
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/textchain.hxx>
27 #include <svx/textchainflow.hxx>
28 #include <svx/sdtacitm.hxx>
29 #include <svx/sdtayitm.hxx>
30 #include <svx/sdtaiitm.hxx>
31 #include <svx/sdtaaitm.hxx>
32 #include <svx/xfillit0.hxx>
33 #include <basegfx/vector/b2dvector.hxx>
34 #include <sdr/primitive2d/sdrtextprimitive2d.hxx>
35 #include <drawinglayer/primitive2d/textprimitive2d.hxx>
36 #include <drawinglayer/primitive2d/textdecoratedprimitive2d.hxx>
37 #include <basegfx/range/b2drange.hxx>
38 #include <editeng/eeitem.hxx>
39 #include <editeng/editstat.hxx>
40 #include <tools/helpers.hxx>
41 #include <svx/sdtfchim.hxx>
42 #include <svl/itemset.hxx>
43 #include <basegfx/polygon/b2dpolygontools.hxx>
44 #include <basegfx/polygon/b2dpolygon.hxx>
45 #include <drawinglayer/animation/animationtiming.hxx>
46 #include <basegfx/color/bcolor.hxx>
47 #include <vcl/svapp.hxx>
48 #include <editeng/escapementitem.hxx>
49 #include <editeng/svxenum.hxx>
50 #include <editeng/flditem.hxx>
51 #include <editeng/adjustitem.hxx>
52 #include <drawinglayer/primitive2d/texthierarchyprimitive2d.hxx>
53 #include <vcl/metaact.hxx>
54 #include <drawinglayer/primitive2d/wrongspellprimitive2d.hxx>
55 #include <drawinglayer/primitive2d/graphicprimitive2d.hxx>
56 #include <drawinglayer/primitive2d/textlayoutdevice.hxx>
57 #include <svx/unoapi.hxx>
58 #include <drawinglayer/geometry/viewinformation2d.hxx>
59 #include <editeng/outlobj.hxx>
60 #include <editeng/editobj.hxx>
61 #include <editeng/overflowingtxt.hxx>
62 #include <basegfx/matrix/b2dhommatrixtools.hxx>
63 #include <sal/log.hxx>
64
65 using namespace com::sun::star;
66
67 // helpers
68
69 namespace
70 {
71 class impTextBreakupHandler
72 {
73 private:
74 drawinglayer::primitive2d::Primitive2DContainer maTextPortionPrimitives;
75 drawinglayer::primitive2d::Primitive2DContainer maLinePrimitives;
76 drawinglayer::primitive2d::Primitive2DContainer maParagraphPrimitives;
77
78 SdrOutliner& mrOutliner;
79 basegfx::B2DHomMatrix maNewTransformA;
80 basegfx::B2DHomMatrix maNewTransformB;
81
82 // the visible area for contour text decomposition
83 basegfx::B2DVector maScale;
84
85 // ClipRange for BlockText decomposition; only text portions completely
86 // inside are to be accepted, so this is different from geometric clipping
87 // (which would allow e.g. upper parts of portions to remain). Only used for
88 // BlockText (see there)
89 basegfx::B2DRange maClipRange;
90
91 DECL_LINK(decomposeContourTextPrimitive, DrawPortionInfo*, void);
92 DECL_LINK(decomposeBlockTextPrimitive, DrawPortionInfo*, void);
93 DECL_LINK(decomposeStretchTextPrimitive, DrawPortionInfo*, void);
94
95 DECL_LINK(decomposeContourBulletPrimitive, DrawBulletInfo*, void);
96 DECL_LINK(decomposeBlockBulletPrimitive, DrawBulletInfo*, void);
97 DECL_LINK(decomposeStretchBulletPrimitive, DrawBulletInfo*, void);
98
99 void impCreateTextPortionPrimitive(const DrawPortionInfo& rInfo);
100 static drawinglayer::primitive2d::BasePrimitive2D* impCheckFieldPrimitive(drawinglayer::primitive2d::BasePrimitive2D* pPrimitive, const DrawPortionInfo& rInfo);
101 void impFlushTextPortionPrimitivesToLinePrimitives();
102 void impFlushLinePrimitivesToParagraphPrimitives(sal_Int32 nPara);
103 void impHandleDrawPortionInfo(const DrawPortionInfo& rInfo);
104 void impHandleDrawBulletInfo(const DrawBulletInfo& rInfo);
105
106 public:
impTextBreakupHandler(SdrOutliner & rOutliner)107 explicit impTextBreakupHandler(SdrOutliner& rOutliner)
108 : maTextPortionPrimitives(),
109 maLinePrimitives(),
110 maParagraphPrimitives(),
111 mrOutliner(rOutliner),
112 maNewTransformA(),
113 maNewTransformB(),
114 maScale(),
115 maClipRange()
116 {
117 }
118
decomposeContourTextPrimitive(const basegfx::B2DHomMatrix & rNewTransformA,const basegfx::B2DHomMatrix & rNewTransformB,const basegfx::B2DVector & rScale)119 void decomposeContourTextPrimitive(const basegfx::B2DHomMatrix& rNewTransformA, const basegfx::B2DHomMatrix& rNewTransformB, const basegfx::B2DVector& rScale)
120 {
121 maScale = rScale;
122 maNewTransformA = rNewTransformA;
123 maNewTransformB = rNewTransformB;
124 mrOutliner.SetDrawPortionHdl(LINK(this, impTextBreakupHandler, decomposeContourTextPrimitive));
125 mrOutliner.SetDrawBulletHdl(LINK(this, impTextBreakupHandler, decomposeContourBulletPrimitive));
126 mrOutliner.StripPortions();
127 mrOutliner.SetDrawPortionHdl(Link<DrawPortionInfo*,void>());
128 mrOutliner.SetDrawBulletHdl(Link<DrawBulletInfo*,void>());
129 }
130
decomposeBlockTextPrimitive(const basegfx::B2DHomMatrix & rNewTransformA,const basegfx::B2DHomMatrix & rNewTransformB,const basegfx::B2DRange & rClipRange)131 void decomposeBlockTextPrimitive(
132 const basegfx::B2DHomMatrix& rNewTransformA,
133 const basegfx::B2DHomMatrix& rNewTransformB,
134 const basegfx::B2DRange& rClipRange)
135 {
136 maNewTransformA = rNewTransformA;
137 maNewTransformB = rNewTransformB;
138 maClipRange = rClipRange;
139 mrOutliner.SetDrawPortionHdl(LINK(this, impTextBreakupHandler, decomposeBlockTextPrimitive));
140 mrOutliner.SetDrawBulletHdl(LINK(this, impTextBreakupHandler, decomposeBlockBulletPrimitive));
141 mrOutliner.StripPortions();
142 mrOutliner.SetDrawPortionHdl(Link<DrawPortionInfo*,void>());
143 mrOutliner.SetDrawBulletHdl(Link<DrawBulletInfo*,void>());
144 }
145
decomposeStretchTextPrimitive(const basegfx::B2DHomMatrix & rNewTransformA,const basegfx::B2DHomMatrix & rNewTransformB)146 void decomposeStretchTextPrimitive(const basegfx::B2DHomMatrix& rNewTransformA, const basegfx::B2DHomMatrix& rNewTransformB)
147 {
148 maNewTransformA = rNewTransformA;
149 maNewTransformB = rNewTransformB;
150 mrOutliner.SetDrawPortionHdl(LINK(this, impTextBreakupHandler, decomposeStretchTextPrimitive));
151 mrOutliner.SetDrawBulletHdl(LINK(this, impTextBreakupHandler, decomposeStretchBulletPrimitive));
152 mrOutliner.StripPortions();
153 mrOutliner.SetDrawPortionHdl(Link<DrawPortionInfo*,void>());
154 mrOutliner.SetDrawBulletHdl(Link<DrawBulletInfo*,void>());
155 }
156
157 drawinglayer::primitive2d::Primitive2DContainer const & getPrimitive2DSequence();
158 };
159
impCreateTextPortionPrimitive(const DrawPortionInfo & rInfo)160 void impTextBreakupHandler::impCreateTextPortionPrimitive(const DrawPortionInfo& rInfo)
161 {
162 if(rInfo.maText.isEmpty() || !rInfo.mnTextLen)
163 return;
164
165 OUString caseMappedText = rInfo.mrFont.CalcCaseMap( rInfo.maText );
166 basegfx::B2DVector aFontScaling;
167 drawinglayer::attribute::FontAttribute aFontAttribute(
168 drawinglayer::primitive2d::getFontAttributeFromVclFont(
169 aFontScaling,
170 rInfo.mrFont,
171 rInfo.IsRTL(),
172 false));
173 basegfx::B2DHomMatrix aNewTransform;
174
175 // add font scale to new transform
176 aNewTransform.scale(aFontScaling.getX(), aFontScaling.getY());
177
178 // look for proportional font scaling, if necessary, scale accordingly
179 if(100 != rInfo.mrFont.GetPropr())
180 {
181 const double fFactor(rInfo.mrFont.GetPropr() / 100.0);
182 aNewTransform.scale(fFactor, fFactor);
183 }
184
185 // apply font rotate
186 if(rInfo.mrFont.GetOrientation())
187 {
188 aNewTransform.rotate(-rInfo.mrFont.GetOrientation() * F_PI1800);
189 }
190
191 // look for escapement, if necessary, translate accordingly
192 if(rInfo.mrFont.GetEscapement())
193 {
194 sal_Int16 nEsc(rInfo.mrFont.GetEscapement());
195
196 if(DFLT_ESC_AUTO_SUPER == nEsc)
197 {
198 nEsc = 33;
199 }
200 else if(DFLT_ESC_AUTO_SUB == nEsc)
201 {
202 nEsc = -20;
203 }
204
205 if(nEsc > 100)
206 {
207 nEsc = 100;
208 }
209 else if(nEsc < -100)
210 {
211 nEsc = -100;
212 }
213
214 const double fEscapement(nEsc / -100.0);
215 aNewTransform.translate(0.0, fEscapement * aFontScaling.getY());
216 }
217
218 // apply transformA
219 aNewTransform *= maNewTransformA;
220
221 // apply local offset
222 aNewTransform.translate(rInfo.mrStartPos.X(), rInfo.mrStartPos.Y());
223
224 // also apply embedding object's transform
225 aNewTransform *= maNewTransformB;
226
227 // prepare DXArray content. To make it independent from font size (and such from
228 // the text transformation), scale it to unit coordinates
229 ::std::vector< double > aDXArray;
230
231 if(rInfo.mpDXArray && rInfo.mnTextLen)
232 {
233 aDXArray.reserve(rInfo.mnTextLen);
234
235 for(sal_Int32 a=0; a < rInfo.mnTextLen; a++)
236 {
237 aDXArray.push_back(static_cast<double>(rInfo.mpDXArray[a]));
238 }
239 }
240
241 // create complex text primitive and append
242 const Color aFontColor(rInfo.mrFont.GetColor());
243 const basegfx::BColor aBFontColor(aFontColor.getBColor());
244
245 const Color aTextFillColor(rInfo.mrFont.GetFillColor());
246
247 // prepare wordLineMode (for underline and strikeout)
248 // NOT for bullet texts. It is set (this may be an error by itself), but needs to be suppressed to hinder e.g. '1)'
249 // to be split which would not look like the original
250 const bool bWordLineMode(rInfo.mrFont.IsWordLineMode() && !rInfo.mbEndOfBullet);
251
252 // prepare new primitive
253 drawinglayer::primitive2d::BasePrimitive2D* pNewPrimitive = nullptr;
254 const bool bDecoratedIsNeeded(
255 LINESTYLE_NONE != rInfo.mrFont.GetOverline()
256 || LINESTYLE_NONE != rInfo.mrFont.GetUnderline()
257 || STRIKEOUT_NONE != rInfo.mrFont.GetStrikeout()
258 || FontEmphasisMark::NONE != (rInfo.mrFont.GetEmphasisMark() & FontEmphasisMark::Style)
259 || FontRelief::NONE != rInfo.mrFont.GetRelief()
260 || rInfo.mrFont.IsShadow()
261 || bWordLineMode);
262
263 if(bDecoratedIsNeeded)
264 {
265 // TextDecoratedPortionPrimitive2D needed, prepare some more data
266 // get overline and underline color. If it's on automatic (0xffffffff) use FontColor instead
267 const Color aUnderlineColor(rInfo.maTextLineColor);
268 const basegfx::BColor aBUnderlineColor((Color(0xffffffff) == aUnderlineColor) ? aBFontColor : aUnderlineColor.getBColor());
269 const Color aOverlineColor(rInfo.maOverlineColor);
270 const basegfx::BColor aBOverlineColor((Color(0xffffffff) == aOverlineColor) ? aBFontColor : aOverlineColor.getBColor());
271
272 // prepare overline and underline data
273 const drawinglayer::primitive2d::TextLine eFontOverline(
274 drawinglayer::primitive2d::mapFontLineStyleToTextLine(rInfo.mrFont.GetOverline()));
275 const drawinglayer::primitive2d::TextLine eFontLineStyle(
276 drawinglayer::primitive2d::mapFontLineStyleToTextLine(rInfo.mrFont.GetUnderline()));
277
278 // check UnderlineAbove
279 const bool bUnderlineAbove(
280 drawinglayer::primitive2d::TEXT_LINE_NONE != eFontLineStyle && rInfo.mrFont.IsUnderlineAbove());
281
282 // prepare strikeout data
283 const drawinglayer::primitive2d::TextStrikeout eTextStrikeout(
284 drawinglayer::primitive2d::mapFontStrikeoutToTextStrikeout(rInfo.mrFont.GetStrikeout()));
285
286 // prepare emphasis mark data
287 drawinglayer::primitive2d::TextEmphasisMark eTextEmphasisMark(drawinglayer::primitive2d::TEXT_FONT_EMPHASIS_MARK_NONE);
288
289 switch(rInfo.mrFont.GetEmphasisMark() & FontEmphasisMark::Style)
290 {
291 case FontEmphasisMark::Dot : eTextEmphasisMark = drawinglayer::primitive2d::TEXT_FONT_EMPHASIS_MARK_DOT; break;
292 case FontEmphasisMark::Circle : eTextEmphasisMark = drawinglayer::primitive2d::TEXT_FONT_EMPHASIS_MARK_CIRCLE; break;
293 case FontEmphasisMark::Disc : eTextEmphasisMark = drawinglayer::primitive2d::TEXT_FONT_EMPHASIS_MARK_DISC; break;
294 case FontEmphasisMark::Accent : eTextEmphasisMark = drawinglayer::primitive2d::TEXT_FONT_EMPHASIS_MARK_ACCENT; break;
295 default: break;
296 }
297
298 const bool bEmphasisMarkAbove(rInfo.mrFont.GetEmphasisMark() & FontEmphasisMark::PosAbove);
299 const bool bEmphasisMarkBelow(rInfo.mrFont.GetEmphasisMark() & FontEmphasisMark::PosBelow);
300
301 // prepare font relief data
302 drawinglayer::primitive2d::TextRelief eTextRelief(drawinglayer::primitive2d::TEXT_RELIEF_NONE);
303
304 switch(rInfo.mrFont.GetRelief())
305 {
306 case FontRelief::Embossed : eTextRelief = drawinglayer::primitive2d::TEXT_RELIEF_EMBOSSED; break;
307 case FontRelief::Engraved : eTextRelief = drawinglayer::primitive2d::TEXT_RELIEF_ENGRAVED; break;
308 default : break; // RELIEF_NONE, FontRelief_FORCE_EQUAL_SIZE
309 }
310
311 // prepare shadow/outline data
312 const bool bShadow(rInfo.mrFont.IsShadow());
313
314 // TextDecoratedPortionPrimitive2D is needed, create one
315 pNewPrimitive = new drawinglayer::primitive2d::TextDecoratedPortionPrimitive2D(
316
317 // attributes for TextSimplePortionPrimitive2D
318 aNewTransform,
319 caseMappedText,
320 rInfo.mnTextStart,
321 rInfo.mnTextLen,
322 aDXArray,
323 aFontAttribute,
324 rInfo.mpLocale ? *rInfo.mpLocale : css::lang::Locale(),
325 aBFontColor,
326 aTextFillColor,
327
328 // attributes for TextDecoratedPortionPrimitive2D
329 aBOverlineColor,
330 aBUnderlineColor,
331 eFontOverline,
332 eFontLineStyle,
333 bUnderlineAbove,
334 eTextStrikeout,
335 bWordLineMode,
336 eTextEmphasisMark,
337 bEmphasisMarkAbove,
338 bEmphasisMarkBelow,
339 eTextRelief,
340 bShadow);
341 }
342 else
343 {
344 // TextSimplePortionPrimitive2D is enough
345 pNewPrimitive = new drawinglayer::primitive2d::TextSimplePortionPrimitive2D(
346 aNewTransform,
347 caseMappedText,
348 rInfo.mnTextStart,
349 rInfo.mnTextLen,
350 aDXArray,
351 aFontAttribute,
352 rInfo.mpLocale ? *rInfo.mpLocale : css::lang::Locale(),
353 aBFontColor,
354 rInfo.mbFilled,
355 rInfo.mnWidthToFill,
356 aTextFillColor);
357 }
358
359 if(rInfo.mbEndOfBullet)
360 {
361 // embed in TextHierarchyBulletPrimitive2D
362 const drawinglayer::primitive2d::Primitive2DReference aNewReference(pNewPrimitive);
363 const drawinglayer::primitive2d::Primitive2DContainer aNewSequence { aNewReference } ;
364 pNewPrimitive = new drawinglayer::primitive2d::TextHierarchyBulletPrimitive2D(aNewSequence);
365 }
366
367 if(rInfo.mpFieldData)
368 {
369 pNewPrimitive = impCheckFieldPrimitive(pNewPrimitive, rInfo);
370 }
371
372 maTextPortionPrimitives.push_back(pNewPrimitive);
373
374 // support for WrongSpellVector. Create WrongSpellPrimitives as needed
375 if(rInfo.mpWrongSpellVector && !aDXArray.empty())
376 {
377 const sal_Int32 nSize(rInfo.mpWrongSpellVector->size());
378 const sal_Int32 nDXCount(aDXArray.size());
379 const basegfx::BColor aSpellColor(1.0, 0.0, 0.0); // red, hard coded
380
381 for(sal_Int32 a(0); a < nSize; a++)
382 {
383 const EEngineData::WrongSpellClass& rCandidate = (*rInfo.mpWrongSpellVector)[a];
384
385 if(rCandidate.nStart >= rInfo.mnTextStart && rCandidate.nEnd >= rInfo.mnTextStart && rCandidate.nEnd > rCandidate.nStart)
386 {
387 const sal_Int32 nStart(rCandidate.nStart - rInfo.mnTextStart);
388 const sal_Int32 nEnd(rCandidate.nEnd - rInfo.mnTextStart);
389 double fStart(0.0);
390 double fEnd(0.0);
391
392 if(nStart > 0 && nStart - 1 < nDXCount)
393 {
394 fStart = aDXArray[nStart - 1];
395 }
396
397 if(nEnd > 0 && nEnd - 1 < nDXCount)
398 {
399 fEnd = aDXArray[nEnd - 1];
400 }
401
402 if(!basegfx::fTools::equal(fStart, fEnd))
403 {
404 if(rInfo.IsRTL())
405 {
406 // #i98523#
407 // When the portion is RTL, mirror the redlining using the
408 // full portion width
409 const double fTextWidth(aDXArray[aDXArray.size() - 1]);
410
411 fStart = fTextWidth - fStart;
412 fEnd = fTextWidth - fEnd;
413 }
414
415 // need to take FontScaling out of values; it's already part of
416 // aNewTransform and would be double applied
417 const double fFontScaleX(aFontScaling.getX());
418
419 if(!basegfx::fTools::equal(fFontScaleX, 1.0)
420 && !basegfx::fTools::equalZero(fFontScaleX))
421 {
422 fStart /= fFontScaleX;
423 fEnd /= fFontScaleX;
424 }
425
426 maTextPortionPrimitives.push_back(new drawinglayer::primitive2d::WrongSpellPrimitive2D(
427 aNewTransform,
428 fStart,
429 fEnd,
430 aSpellColor));
431 }
432 }
433 }
434 }
435 }
436
impCheckFieldPrimitive(drawinglayer::primitive2d::BasePrimitive2D * pPrimitive,const DrawPortionInfo & rInfo)437 drawinglayer::primitive2d::BasePrimitive2D* impTextBreakupHandler::impCheckFieldPrimitive(drawinglayer::primitive2d::BasePrimitive2D* pPrimitive, const DrawPortionInfo& rInfo)
438 {
439 if(rInfo.mpFieldData)
440 {
441 // Support for FIELD_SEQ_BEGIN, FIELD_SEQ_END. If used, create a TextHierarchyFieldPrimitive2D
442 // which holds the field type and, if applicable, the URL
443 const SvxURLField* pURLField = dynamic_cast< const SvxURLField* >(rInfo.mpFieldData);
444 const SvxPageField* pPageField = dynamic_cast< const SvxPageField* >(rInfo.mpFieldData);
445
446 // embed current primitive to a sequence
447 drawinglayer::primitive2d::Primitive2DContainer aSequence;
448
449 if(pPrimitive)
450 {
451 aSequence.resize(1);
452 aSequence[0] = drawinglayer::primitive2d::Primitive2DReference(pPrimitive);
453 }
454
455 if(pURLField)
456 {
457 // extended this to hold more of the contents of the original
458 // SvxURLField since that stuff is still used in HitTest and e.g. Calc
459 std::vector< std::pair< OUString, OUString>> meValues;
460 meValues.emplace_back("URL", pURLField->GetURL());
461 meValues.emplace_back("Representation", pURLField->GetRepresentation());
462 meValues.emplace_back("TargetFrame", pURLField->GetTargetFrame());
463 meValues.emplace_back("SvxURLFormat", OUString::number(static_cast<sal_uInt16>(pURLField->GetFormat())));
464 pPrimitive = new drawinglayer::primitive2d::TextHierarchyFieldPrimitive2D(aSequence, drawinglayer::primitive2d::FIELD_TYPE_URL, &meValues);
465 }
466 else if(pPageField)
467 {
468 pPrimitive = new drawinglayer::primitive2d::TextHierarchyFieldPrimitive2D(aSequence, drawinglayer::primitive2d::FIELD_TYPE_PAGE);
469 }
470 else
471 {
472 pPrimitive = new drawinglayer::primitive2d::TextHierarchyFieldPrimitive2D(aSequence, drawinglayer::primitive2d::FIELD_TYPE_COMMON);
473 }
474 }
475
476 return pPrimitive;
477 }
478
impFlushTextPortionPrimitivesToLinePrimitives()479 void impTextBreakupHandler::impFlushTextPortionPrimitivesToLinePrimitives()
480 {
481 // only create a line primitive when we had content; there is no need for
482 // empty line primitives (contrary to paragraphs, see below).
483 if(!maTextPortionPrimitives.empty())
484 {
485 maLinePrimitives.push_back(new drawinglayer::primitive2d::TextHierarchyLinePrimitive2D(maTextPortionPrimitives));
486 maTextPortionPrimitives.clear();
487 }
488 }
489
impFlushLinePrimitivesToParagraphPrimitives(sal_Int32 nPara)490 void impTextBreakupHandler::impFlushLinePrimitivesToParagraphPrimitives(sal_Int32 nPara)
491 {
492 sal_Int16 nDepth = mrOutliner.GetDepth(nPara);
493 EBulletInfo eInfo = mrOutliner.GetBulletInfo(nPara);
494 // Pass -1 to signal VclMetafileProcessor2D that there is no active
495 // bullets/numbering in this paragraph (i.e. this is normal text)
496 const sal_Int16 nOutlineLevel( eInfo.bVisible ? nDepth : -1);
497
498 // ALWAYS create a paragraph primitive, even when no content was added. This is done to
499 // have the correct paragraph count even with empty paragraphs. Those paragraphs will
500 // have an empty sub-PrimitiveSequence.
501 maParagraphPrimitives.push_back(
502 new drawinglayer::primitive2d::TextHierarchyParagraphPrimitive2D(
503 maLinePrimitives,
504 nOutlineLevel));
505 maLinePrimitives.clear();
506 }
507
impHandleDrawPortionInfo(const DrawPortionInfo & rInfo)508 void impTextBreakupHandler::impHandleDrawPortionInfo(const DrawPortionInfo& rInfo)
509 {
510 impCreateTextPortionPrimitive(rInfo);
511
512 if(rInfo.mbEndOfLine || rInfo.mbEndOfParagraph)
513 {
514 impFlushTextPortionPrimitivesToLinePrimitives();
515 }
516
517 if(rInfo.mbEndOfParagraph)
518 {
519 impFlushLinePrimitivesToParagraphPrimitives(rInfo.mnPara);
520 }
521 }
522
impHandleDrawBulletInfo(const DrawBulletInfo & rInfo)523 void impTextBreakupHandler::impHandleDrawBulletInfo(const DrawBulletInfo& rInfo)
524 {
525 basegfx::B2DHomMatrix aNewTransform;
526
527 // add size to new transform
528 aNewTransform.scale(rInfo.maBulletSize.getWidth(), rInfo.maBulletSize.getHeight());
529
530 // apply transformA
531 aNewTransform *= maNewTransformA;
532
533 // apply local offset
534 aNewTransform.translate(rInfo.maBulletPosition.X(), rInfo.maBulletPosition.Y());
535
536 // also apply embedding object's transform
537 aNewTransform *= maNewTransformB;
538
539 // prepare empty GraphicAttr
540 const GraphicAttr aGraphicAttr;
541
542 // create GraphicPrimitive2D
543 const drawinglayer::primitive2d::Primitive2DReference aNewReference(new drawinglayer::primitive2d::GraphicPrimitive2D(
544 aNewTransform,
545 rInfo.maBulletGraphicObject,
546 aGraphicAttr));
547
548 // embed in TextHierarchyBulletPrimitive2D
549 const drawinglayer::primitive2d::Primitive2DContainer aNewSequence { aNewReference };
550 drawinglayer::primitive2d::BasePrimitive2D* pNewPrimitive = new drawinglayer::primitive2d::TextHierarchyBulletPrimitive2D(aNewSequence);
551
552 // add to output
553 maTextPortionPrimitives.push_back(pNewPrimitive);
554 }
555
IMPL_LINK(impTextBreakupHandler,decomposeContourTextPrimitive,DrawPortionInfo *,pInfo,void)556 IMPL_LINK(impTextBreakupHandler, decomposeContourTextPrimitive, DrawPortionInfo*, pInfo, void)
557 {
558 // for contour text, ignore (clip away) all portions which are below
559 // the visible area given by maScale
560 if(pInfo && static_cast<double>(pInfo->mrStartPos.Y()) < maScale.getY())
561 {
562 impHandleDrawPortionInfo(*pInfo);
563 }
564 }
565
IMPL_LINK(impTextBreakupHandler,decomposeBlockTextPrimitive,DrawPortionInfo *,pInfo,void)566 IMPL_LINK(impTextBreakupHandler, decomposeBlockTextPrimitive, DrawPortionInfo*, pInfo, void)
567 {
568 if(pInfo)
569 {
570 // Is clipping wanted? This is text clipping; only accept a portion
571 // if it's completely in the range
572 if(!maClipRange.isEmpty())
573 {
574 // Test start position first; this allows to not get the text range at
575 // all if text is far outside
576 const basegfx::B2DPoint aStartPosition(pInfo->mrStartPos.X(), pInfo->mrStartPos.Y());
577
578 if(!maClipRange.isInside(aStartPosition))
579 {
580 return;
581 }
582
583 // Start position is inside. Get TextBoundRect and TopLeft next
584 drawinglayer::primitive2d::TextLayouterDevice aTextLayouterDevice;
585 aTextLayouterDevice.setFont(pInfo->mrFont);
586
587 const basegfx::B2DRange aTextBoundRect(
588 aTextLayouterDevice.getTextBoundRect(
589 pInfo->maText, pInfo->mnTextStart, pInfo->mnTextLen));
590 const basegfx::B2DPoint aTopLeft(aTextBoundRect.getMinimum() + aStartPosition);
591
592 if(!maClipRange.isInside(aTopLeft))
593 {
594 return;
595 }
596
597 // TopLeft is inside. Get BottomRight and check
598 const basegfx::B2DPoint aBottomRight(aTextBoundRect.getMaximum() + aStartPosition);
599
600 if(!maClipRange.isInside(aBottomRight))
601 {
602 return;
603 }
604
605 // all inside, clip was successful
606 }
607 impHandleDrawPortionInfo(*pInfo);
608 }
609 }
610
IMPL_LINK(impTextBreakupHandler,decomposeStretchTextPrimitive,DrawPortionInfo *,pInfo,void)611 IMPL_LINK(impTextBreakupHandler, decomposeStretchTextPrimitive, DrawPortionInfo*, pInfo, void)
612 {
613 if(pInfo)
614 {
615 impHandleDrawPortionInfo(*pInfo);
616 }
617 }
618
IMPL_LINK(impTextBreakupHandler,decomposeContourBulletPrimitive,DrawBulletInfo *,pInfo,void)619 IMPL_LINK(impTextBreakupHandler, decomposeContourBulletPrimitive, DrawBulletInfo*, pInfo, void)
620 {
621 if(pInfo)
622 {
623 impHandleDrawBulletInfo(*pInfo);
624 }
625 }
626
IMPL_LINK(impTextBreakupHandler,decomposeBlockBulletPrimitive,DrawBulletInfo *,pInfo,void)627 IMPL_LINK(impTextBreakupHandler, decomposeBlockBulletPrimitive, DrawBulletInfo*, pInfo, void)
628 {
629 if(pInfo)
630 {
631 impHandleDrawBulletInfo(*pInfo);
632 }
633 }
634
IMPL_LINK(impTextBreakupHandler,decomposeStretchBulletPrimitive,DrawBulletInfo *,pInfo,void)635 IMPL_LINK(impTextBreakupHandler, decomposeStretchBulletPrimitive, DrawBulletInfo*, pInfo, void)
636 {
637 if(pInfo)
638 {
639 impHandleDrawBulletInfo(*pInfo);
640 }
641 }
642
getPrimitive2DSequence()643 drawinglayer::primitive2d::Primitive2DContainer const & impTextBreakupHandler::getPrimitive2DSequence()
644 {
645 if(!maTextPortionPrimitives.empty())
646 {
647 // collect non-closed lines
648 impFlushTextPortionPrimitivesToLinePrimitives();
649 }
650
651 if(!maLinePrimitives.empty())
652 {
653 // collect non-closed paragraphs
654 impFlushLinePrimitivesToParagraphPrimitives(mrOutliner.GetParagraphCount() - 1);
655 }
656
657 return maParagraphPrimitives;
658 }
659 } // end of anonymous namespace
660
661
662 // primitive decompositions
663
impDecomposeContourTextPrimitive(drawinglayer::primitive2d::Primitive2DContainer & rTarget,const drawinglayer::primitive2d::SdrContourTextPrimitive2D & rSdrContourTextPrimitive,const drawinglayer::geometry::ViewInformation2D & aViewInformation) const664 void SdrTextObj::impDecomposeContourTextPrimitive(
665 drawinglayer::primitive2d::Primitive2DContainer& rTarget,
666 const drawinglayer::primitive2d::SdrContourTextPrimitive2D& rSdrContourTextPrimitive,
667 const drawinglayer::geometry::ViewInformation2D& aViewInformation) const
668 {
669 // decompose matrix to have position and size of text
670 basegfx::B2DVector aScale, aTranslate;
671 double fRotate, fShearX;
672 rSdrContourTextPrimitive.getObjectTransform().decompose(aScale, aTranslate, fRotate, fShearX);
673
674 // prepare contour polygon, force to non-mirrored for laying out
675 basegfx::B2DPolyPolygon aPolyPolygon(rSdrContourTextPrimitive.getUnitPolyPolygon());
676 aPolyPolygon.transform(basegfx::utils::createScaleB2DHomMatrix(fabs(aScale.getX()), fabs(aScale.getY())));
677
678 // prepare outliner
679 SolarMutexGuard aSolarGuard;
680 SdrOutliner& rOutliner = ImpGetDrawOutliner();
681 const Size aNullSize;
682 rOutliner.SetPaperSize(aNullSize);
683 rOutliner.SetPolygon(aPolyPolygon);
684 rOutliner.SetUpdateMode(true);
685 rOutliner.SetText(rSdrContourTextPrimitive.getOutlinerParaObject());
686
687 // set visualizing page at Outliner; needed e.g. for PageNumberField decomposition
688 rOutliner.setVisualizedPage(GetSdrPageFromXDrawPage(aViewInformation.getVisualizedPage()));
689
690 // prepare matrices to apply to newly created primitives
691 basegfx::B2DHomMatrix aNewTransformA;
692
693 // mirroring. We are now in the polygon sizes. When mirroring in X and Y,
694 // move the null point which was top left to bottom right.
695 const bool bMirrorX(basegfx::fTools::less(aScale.getX(), 0.0));
696 const bool bMirrorY(basegfx::fTools::less(aScale.getY(), 0.0));
697
698 // in-between the translations of the single primitives will take place. Afterwards,
699 // the object's transformations need to be applied
700 const basegfx::B2DHomMatrix aNewTransformB(basegfx::utils::createScaleShearXRotateTranslateB2DHomMatrix(
701 bMirrorX ? -1.0 : 1.0, bMirrorY ? -1.0 : 1.0,
702 fShearX, fRotate, aTranslate.getX(), aTranslate.getY()));
703
704 // now break up text primitives.
705 impTextBreakupHandler aConverter(rOutliner);
706 aConverter.decomposeContourTextPrimitive(aNewTransformA, aNewTransformB, aScale);
707
708 // cleanup outliner
709 rOutliner.Clear();
710 rOutliner.setVisualizedPage(nullptr);
711
712 rTarget = aConverter.getPrimitive2DSequence();
713 }
714
impDecomposeAutoFitTextPrimitive(drawinglayer::primitive2d::Primitive2DContainer & rTarget,const drawinglayer::primitive2d::SdrAutoFitTextPrimitive2D & rSdrAutofitTextPrimitive,const drawinglayer::geometry::ViewInformation2D & aViewInformation) const715 void SdrTextObj::impDecomposeAutoFitTextPrimitive(
716 drawinglayer::primitive2d::Primitive2DContainer& rTarget,
717 const drawinglayer::primitive2d::SdrAutoFitTextPrimitive2D& rSdrAutofitTextPrimitive,
718 const drawinglayer::geometry::ViewInformation2D& aViewInformation) const
719 {
720 // decompose matrix to have position and size of text
721 basegfx::B2DVector aScale, aTranslate;
722 double fRotate, fShearX;
723 rSdrAutofitTextPrimitive.getTextRangeTransform().decompose(aScale, aTranslate, fRotate, fShearX);
724
725 // use B2DRange aAnchorTextRange for calculations
726 basegfx::B2DRange aAnchorTextRange(aTranslate);
727 aAnchorTextRange.expand(aTranslate + aScale);
728
729 // prepare outliner
730 const SfxItemSet& rTextItemSet = rSdrAutofitTextPrimitive.getSdrText()->GetItemSet();
731 SolarMutexGuard aSolarGuard;
732 SdrOutliner& rOutliner = ImpGetDrawOutliner();
733 SdrTextVertAdjust eVAdj = GetTextVerticalAdjust(rTextItemSet);
734 SdrTextHorzAdjust eHAdj = GetTextHorizontalAdjust(rTextItemSet);
735 const EEControlBits nOriginalControlWord(rOutliner.GetControlWord());
736 const Size aNullSize;
737
738 // set visualizing page at Outliner; needed e.g. for PageNumberField decomposition
739 rOutliner.setVisualizedPage(GetSdrPageFromXDrawPage(aViewInformation.getVisualizedPage()));
740
741 rOutliner.SetControlWord(nOriginalControlWord|EEControlBits::AUTOPAGESIZE|EEControlBits::STRETCHING);
742 rOutliner.SetMinAutoPaperSize(aNullSize);
743 rOutliner.SetMaxAutoPaperSize(Size(1000000,1000000));
744
745 // add one to rage sizes to get back to the old Rectangle and outliner measurements
746 const sal_uInt32 nAnchorTextWidth(FRound(aAnchorTextRange.getWidth() + 1));
747 const sal_uInt32 nAnchorTextHeight(FRound(aAnchorTextRange.getHeight() + 1));
748 const OutlinerParaObject* pOutlinerParaObject = rSdrAutofitTextPrimitive.getSdrText()->GetOutlinerParaObject();
749 OSL_ENSURE(pOutlinerParaObject, "impDecomposeBlockTextPrimitive used with no OutlinerParaObject (!)");
750 const bool bVerticalWriting(pOutlinerParaObject->IsVertical());
751 const bool bTopToBottom(pOutlinerParaObject->IsTopToBottom());
752 const Size aAnchorTextSize(Size(nAnchorTextWidth, nAnchorTextHeight));
753
754 if(rSdrAutofitTextPrimitive.getWordWrap() || IsTextFrame())
755 {
756 rOutliner.SetMaxAutoPaperSize(aAnchorTextSize);
757 }
758
759 if(SDRTEXTHORZADJUST_BLOCK == eHAdj && !bVerticalWriting)
760 {
761 rOutliner.SetMinAutoPaperSize(Size(nAnchorTextWidth, 0));
762 }
763
764 if(SDRTEXTVERTADJUST_BLOCK == eVAdj && bVerticalWriting)
765 {
766 rOutliner.SetMinAutoPaperSize(Size(0, nAnchorTextHeight));
767 }
768
769 rOutliner.SetPaperSize(aNullSize);
770 rOutliner.SetUpdateMode(true);
771 rOutliner.SetText(*pOutlinerParaObject);
772 ImpAutoFitText(rOutliner,aAnchorTextSize,bVerticalWriting);
773
774 // set visualizing page at Outliner; needed e.g. for PageNumberField decomposition
775 rOutliner.setVisualizedPage(GetSdrPageFromXDrawPage(aViewInformation.getVisualizedPage()));
776
777 // now get back the layouted text size from outliner
778 const Size aOutlinerTextSize(rOutliner.GetPaperSize());
779 const basegfx::B2DVector aOutlinerScale(aOutlinerTextSize.Width(), aOutlinerTextSize.Height());
780 basegfx::B2DVector aAdjustTranslate(0.0, 0.0);
781
782 // correct horizontal translation using the now known text size
783 if(SDRTEXTHORZADJUST_CENTER == eHAdj || SDRTEXTHORZADJUST_RIGHT == eHAdj)
784 {
785 const double fFree(aAnchorTextRange.getWidth() - aOutlinerScale.getX());
786
787 if(SDRTEXTHORZADJUST_CENTER == eHAdj)
788 {
789 aAdjustTranslate.setX(fFree / 2.0);
790 }
791
792 if(SDRTEXTHORZADJUST_RIGHT == eHAdj)
793 {
794 aAdjustTranslate.setX(fFree);
795 }
796 }
797
798 // correct vertical translation using the now known text size
799 if(SDRTEXTVERTADJUST_CENTER == eVAdj || SDRTEXTVERTADJUST_BOTTOM == eVAdj)
800 {
801 const double fFree(aAnchorTextRange.getHeight() - aOutlinerScale.getY());
802
803 if(SDRTEXTVERTADJUST_CENTER == eVAdj)
804 {
805 aAdjustTranslate.setY(fFree / 2.0);
806 }
807
808 if(SDRTEXTVERTADJUST_BOTTOM == eVAdj)
809 {
810 aAdjustTranslate.setY(fFree);
811 }
812 }
813
814 // prepare matrices to apply to newly created primitives. aNewTransformA
815 // will get coordinates in aOutlinerScale size and positive in X, Y.
816 basegfx::B2DHomMatrix aNewTransformA;
817 basegfx::B2DHomMatrix aNewTransformB;
818
819 // translate relative to given primitive to get same rotation and shear
820 // as the master shape we are working on. For vertical, use the top-right
821 // corner
822 const double fStartInX(bVerticalWriting && bTopToBottom ? aAdjustTranslate.getX() + aOutlinerScale.getX() : aAdjustTranslate.getX());
823 const double fStartInY(bVerticalWriting && !bTopToBottom ? aAdjustTranslate.getY() + aOutlinerScale.getY() : aAdjustTranslate.getY());
824 aNewTransformA.translate(fStartInX, fStartInY);
825
826 // mirroring. We are now in aAnchorTextRange sizes. When mirroring in X and Y,
827 // move the null point which was top left to bottom right.
828 const bool bMirrorX(basegfx::fTools::less(aScale.getX(), 0.0));
829 const bool bMirrorY(basegfx::fTools::less(aScale.getY(), 0.0));
830 aNewTransformB.scale(bMirrorX ? -1.0 : 1.0, bMirrorY ? -1.0 : 1.0);
831
832 // in-between the translations of the single primitives will take place. Afterwards,
833 // the object's transformations need to be applied
834 aNewTransformB.shearX(fShearX);
835 aNewTransformB.rotate(fRotate);
836 aNewTransformB.translate(aTranslate.getX(), aTranslate.getY());
837
838 basegfx::B2DRange aClipRange;
839
840 // now break up text primitives.
841 impTextBreakupHandler aConverter(rOutliner);
842 aConverter.decomposeBlockTextPrimitive(aNewTransformA, aNewTransformB, aClipRange);
843
844 // cleanup outliner
845 rOutliner.Clear();
846 rOutliner.setVisualizedPage(nullptr);
847 rOutliner.SetControlWord(nOriginalControlWord);
848
849 rTarget = aConverter.getPrimitive2DSequence();
850 }
851
852 // Resolves: fdo#35779 set background color of this shape as the editeng background if there
853 // is one. Check the shape itself, then the host page, then that page's master page.
setSuitableOutlinerBg(::Outliner & rOutliner) const854 void SdrObject::setSuitableOutlinerBg(::Outliner& rOutliner) const
855 {
856 const SfxItemSet* pBackgroundFillSet = &GetObjectItemSet();
857
858 if (drawing::FillStyle_NONE == pBackgroundFillSet->Get(XATTR_FILLSTYLE).GetValue())
859 {
860 SdrPage* pOwnerPage(getSdrPageFromSdrObject());
861 if (pOwnerPage)
862 {
863 pBackgroundFillSet = &pOwnerPage->getSdrPageProperties().GetItemSet();
864
865 if (drawing::FillStyle_NONE == pBackgroundFillSet->Get(XATTR_FILLSTYLE).GetValue())
866 {
867 if (!pOwnerPage->IsMasterPage() && pOwnerPage->TRG_HasMasterPage())
868 {
869 pBackgroundFillSet = &pOwnerPage->TRG_GetMasterPage().getSdrPageProperties().GetItemSet();
870 }
871 }
872 }
873 }
874
875 if (drawing::FillStyle_NONE != pBackgroundFillSet->Get(XATTR_FILLSTYLE).GetValue())
876 {
877 Color aColor(rOutliner.GetBackgroundColor());
878 GetDraftFillColor(*pBackgroundFillSet, aColor);
879 rOutliner.SetBackgroundColor(aColor);
880 }
881 }
882
impDecomposeBlockTextPrimitive(drawinglayer::primitive2d::Primitive2DContainer & rTarget,const drawinglayer::primitive2d::SdrBlockTextPrimitive2D & rSdrBlockTextPrimitive,const drawinglayer::geometry::ViewInformation2D & aViewInformation) const883 void SdrTextObj::impDecomposeBlockTextPrimitive(
884 drawinglayer::primitive2d::Primitive2DContainer& rTarget,
885 const drawinglayer::primitive2d::SdrBlockTextPrimitive2D& rSdrBlockTextPrimitive,
886 const drawinglayer::geometry::ViewInformation2D& aViewInformation) const
887 {
888 // decompose matrix to have position and size of text
889 basegfx::B2DVector aScale, aTranslate;
890 double fRotate, fShearX;
891 rSdrBlockTextPrimitive.getTextRangeTransform().decompose(aScale, aTranslate, fRotate, fShearX);
892
893 // use B2DRange aAnchorTextRange for calculations
894 basegfx::B2DRange aAnchorTextRange(aTranslate);
895 aAnchorTextRange.expand(aTranslate + aScale);
896
897 // prepare outliner
898 const bool bIsCell(rSdrBlockTextPrimitive.getCellText());
899 SolarMutexGuard aSolarGuard;
900 SdrOutliner& rOutliner = ImpGetDrawOutliner();
901 SdrTextHorzAdjust eHAdj = rSdrBlockTextPrimitive.getSdrTextHorzAdjust();
902 SdrTextVertAdjust eVAdj = rSdrBlockTextPrimitive.getSdrTextVertAdjust();
903 const EEControlBits nOriginalControlWord(rOutliner.GetControlWord());
904 const Size aNullSize;
905
906 // set visualizing page at Outliner; needed e.g. for PageNumberField decomposition
907 rOutliner.setVisualizedPage(GetSdrPageFromXDrawPage(aViewInformation.getVisualizedPage()));
908 rOutliner.SetFixedCellHeight(rSdrBlockTextPrimitive.isFixedCellHeight());
909 rOutliner.SetControlWord(nOriginalControlWord|EEControlBits::AUTOPAGESIZE);
910 rOutliner.SetMinAutoPaperSize(aNullSize);
911 rOutliner.SetMaxAutoPaperSize(Size(1000000,1000000));
912
913 // That color needs to be restored on leaving this method
914 Color aOriginalBackColor(rOutliner.GetBackgroundColor());
915 setSuitableOutlinerBg(rOutliner);
916
917 // add one to rage sizes to get back to the old Rectangle and outliner measurements
918 const sal_uInt32 nAnchorTextWidth(FRound(aAnchorTextRange.getWidth() + 1));
919 const sal_uInt32 nAnchorTextHeight(FRound(aAnchorTextRange.getHeight() + 1));
920 const bool bVerticalWriting(rSdrBlockTextPrimitive.getOutlinerParaObject().IsVertical());
921 const bool bTopToBottom(rSdrBlockTextPrimitive.getOutlinerParaObject().IsTopToBottom());
922 const Size aAnchorTextSize(Size(nAnchorTextWidth, nAnchorTextHeight));
923
924 if(bIsCell)
925 {
926 // cell text is formatted neither like a text object nor like an object
927 // text, so use a special setup here
928 rOutliner.SetMaxAutoPaperSize(aAnchorTextSize);
929
930 // #i106214# To work with an unchangeable PaperSize (CellSize in
931 // this case) Set(Min|Max)AutoPaperSize and SetPaperSize have to be used.
932 // #i106214# This was not completely correct; to still measure the real
933 // text height to allow vertical adjust (and vice versa for VerticalWritintg)
934 // only one aspect has to be set, but the other one to zero
935 if(bVerticalWriting)
936 {
937 // measure the horizontal text size
938 rOutliner.SetMinAutoPaperSize(Size(0, aAnchorTextSize.Height()));
939 }
940 else
941 {
942 // measure the vertical text size
943 rOutliner.SetMinAutoPaperSize(Size(aAnchorTextSize.Width(), 0));
944 }
945
946 rOutliner.SetPaperSize(aAnchorTextSize);
947 rOutliner.SetUpdateMode(true);
948 rOutliner.SetText(rSdrBlockTextPrimitive.getOutlinerParaObject());
949 }
950 else
951 {
952 // check if block text is used (only one of them can be true)
953 const bool bHorizontalIsBlock(SDRTEXTHORZADJUST_BLOCK == eHAdj && !bVerticalWriting);
954 const bool bVerticalIsBlock(SDRTEXTVERTADJUST_BLOCK == eVAdj && bVerticalWriting);
955
956 // set minimal paper size horizontally/vertically if needed
957 if(bHorizontalIsBlock)
958 {
959 rOutliner.SetMinAutoPaperSize(Size(nAnchorTextWidth, 0));
960 }
961 else if(bVerticalIsBlock)
962 {
963 rOutliner.SetMinAutoPaperSize(Size(0, nAnchorTextHeight));
964 }
965
966 if((rSdrBlockTextPrimitive.getWordWrap() || IsTextFrame()) && !rSdrBlockTextPrimitive.getUnlimitedPage())
967 {
968 // #i103454# maximal paper size hor/ver needs to be limited to text
969 // frame size. If it's block text, still allow the 'other' direction
970 // to grow to get a correct real text size when using GetPaperSize().
971 // When just using aAnchorTextSize as maximum, GetPaperSize()
972 // would just return aAnchorTextSize again: this means, the wanted
973 // 'measurement' of the real size of block text would not work
974 Size aMaxAutoPaperSize(aAnchorTextSize);
975
976 // Usual processing - always grow in one of directions
977 bool bAllowGrowVertical = !bVerticalWriting;
978 bool bAllowGrowHorizontal = bVerticalWriting;
979
980 // Compatibility mode for tdf#99729
981 if (getSdrModelFromSdrObject().IsAnchoredTextOverflowLegacy())
982 {
983 bAllowGrowVertical = bHorizontalIsBlock;
984 bAllowGrowHorizontal = bVerticalIsBlock;
985 }
986
987 if (bAllowGrowVertical)
988 {
989 // allow to grow vertical for horizontal texts
990 aMaxAutoPaperSize.setHeight(1000000);
991 }
992 else if (bAllowGrowHorizontal)
993 {
994 // allow to grow horizontal for vertical texts
995 aMaxAutoPaperSize.setWidth(1000000);
996 }
997
998 rOutliner.SetMaxAutoPaperSize(aMaxAutoPaperSize);
999 }
1000
1001 rOutliner.SetPaperSize(aNullSize);
1002 rOutliner.SetUpdateMode(true);
1003 rOutliner.SetText(rSdrBlockTextPrimitive.getOutlinerParaObject());
1004 }
1005
1006 rOutliner.SetControlWord(nOriginalControlWord);
1007
1008 // now get back the layouted text size from outliner
1009 const Size aOutlinerTextSize(rOutliner.GetPaperSize());
1010 const basegfx::B2DVector aOutlinerScale(aOutlinerTextSize.Width(), aOutlinerTextSize.Height());
1011 basegfx::B2DVector aAdjustTranslate(0.0, 0.0);
1012
1013 // For draw objects containing text correct hor/ver alignment if text is bigger
1014 // than the object itself. Without that correction, the text would always be
1015 // formatted to the left edge (or top edge when vertical) of the draw object.
1016 if(!IsTextFrame() && !bIsCell)
1017 {
1018 if(aAnchorTextRange.getWidth() < aOutlinerScale.getX() && !bVerticalWriting)
1019 {
1020 // Horizontal case here. Correct only if eHAdj == SDRTEXTHORZADJUST_BLOCK,
1021 // else the alignment is wanted.
1022 if(SDRTEXTHORZADJUST_BLOCK == eHAdj)
1023 {
1024 SvxAdjust eAdjust = GetObjectItemSet().Get(EE_PARA_JUST).GetAdjust();
1025 switch(eAdjust)
1026 {
1027 case SvxAdjust::Left: eHAdj = SDRTEXTHORZADJUST_LEFT; break;
1028 case SvxAdjust::Right: eHAdj = SDRTEXTHORZADJUST_RIGHT; break;
1029 case SvxAdjust::Center: eHAdj = SDRTEXTHORZADJUST_CENTER; break;
1030 default: break;
1031 }
1032 }
1033 }
1034
1035 if(aAnchorTextRange.getHeight() < aOutlinerScale.getY() && bVerticalWriting)
1036 {
1037 // Vertical case here. Correct only if eHAdj == SDRTEXTVERTADJUST_BLOCK,
1038 // else the alignment is wanted.
1039 if(SDRTEXTVERTADJUST_BLOCK == eVAdj)
1040 {
1041 eVAdj = SDRTEXTVERTADJUST_CENTER;
1042 }
1043 }
1044 }
1045
1046 // correct horizontal translation using the now known text size
1047 if(SDRTEXTHORZADJUST_CENTER == eHAdj || SDRTEXTHORZADJUST_RIGHT == eHAdj)
1048 {
1049 const double fFree(aAnchorTextRange.getWidth() - aOutlinerScale.getX());
1050
1051 if(SDRTEXTHORZADJUST_CENTER == eHAdj)
1052 {
1053 aAdjustTranslate.setX(fFree / 2.0);
1054 }
1055
1056 if(SDRTEXTHORZADJUST_RIGHT == eHAdj)
1057 {
1058 aAdjustTranslate.setX(fFree);
1059 }
1060 }
1061
1062 // correct vertical translation using the now known text size
1063 if(SDRTEXTVERTADJUST_CENTER == eVAdj || SDRTEXTVERTADJUST_BOTTOM == eVAdj)
1064 {
1065 const double fFree(aAnchorTextRange.getHeight() - aOutlinerScale.getY());
1066
1067 if(SDRTEXTVERTADJUST_CENTER == eVAdj)
1068 {
1069 aAdjustTranslate.setY(fFree / 2.0);
1070 }
1071
1072 if(SDRTEXTVERTADJUST_BOTTOM == eVAdj)
1073 {
1074 aAdjustTranslate.setY(fFree);
1075 }
1076 }
1077
1078 // prepare matrices to apply to newly created primitives. aNewTransformA
1079 // will get coordinates in aOutlinerScale size and positive in X, Y.
1080 // Translate relative to given primitive to get same rotation and shear
1081 // as the master shape we are working on. For vertical, use the top-right
1082 // corner
1083 const double fStartInX(bVerticalWriting && bTopToBottom ? aAdjustTranslate.getX() + aOutlinerScale.getX() : aAdjustTranslate.getX());
1084 const double fStartInY(bVerticalWriting && !bTopToBottom ? aAdjustTranslate.getY() + aOutlinerScale.getY() : aAdjustTranslate.getY());
1085 const basegfx::B2DTuple aAdjOffset(fStartInX, fStartInY);
1086 basegfx::B2DHomMatrix aNewTransformA(basegfx::utils::createTranslateB2DHomMatrix(aAdjOffset.getX(), aAdjOffset.getY()));
1087
1088 // mirroring. We are now in aAnchorTextRange sizes. When mirroring in X and Y,
1089 // move the null point which was top left to bottom right.
1090 const bool bMirrorX(basegfx::fTools::less(aScale.getX(), 0.0));
1091 const bool bMirrorY(basegfx::fTools::less(aScale.getY(), 0.0));
1092
1093 // in-between the translations of the single primitives will take place. Afterwards,
1094 // the object's transformations need to be applied
1095 const basegfx::B2DHomMatrix aNewTransformB(basegfx::utils::createScaleShearXRotateTranslateB2DHomMatrix(
1096 bMirrorX ? -1.0 : 1.0, bMirrorY ? -1.0 : 1.0,
1097 fShearX, fRotate, aTranslate.getX(), aTranslate.getY()));
1098
1099 // create ClipRange (if needed)
1100 basegfx::B2DRange aClipRange;
1101
1102 if(rSdrBlockTextPrimitive.getClipOnBounds())
1103 {
1104 aClipRange.expand(-aAdjOffset);
1105 aClipRange.expand(basegfx::B2DTuple(aAnchorTextSize.Width(), aAnchorTextSize.Height()) - aAdjOffset);
1106 }
1107
1108 // now break up text primitives.
1109 impTextBreakupHandler aConverter(rOutliner);
1110 aConverter.decomposeBlockTextPrimitive(aNewTransformA, aNewTransformB, aClipRange);
1111
1112 // cleanup outliner
1113 rOutliner.SetBackgroundColor(aOriginalBackColor);
1114 rOutliner.Clear();
1115 rOutliner.setVisualizedPage(nullptr);
1116
1117 rTarget = aConverter.getPrimitive2DSequence();
1118 }
1119
impDecomposeStretchTextPrimitive(drawinglayer::primitive2d::Primitive2DContainer & rTarget,const drawinglayer::primitive2d::SdrStretchTextPrimitive2D & rSdrStretchTextPrimitive,const drawinglayer::geometry::ViewInformation2D & aViewInformation) const1120 void SdrTextObj::impDecomposeStretchTextPrimitive(
1121 drawinglayer::primitive2d::Primitive2DContainer& rTarget,
1122 const drawinglayer::primitive2d::SdrStretchTextPrimitive2D& rSdrStretchTextPrimitive,
1123 const drawinglayer::geometry::ViewInformation2D& aViewInformation) const
1124 {
1125 // decompose matrix to have position and size of text
1126 basegfx::B2DVector aScale, aTranslate;
1127 double fRotate, fShearX;
1128 rSdrStretchTextPrimitive.getTextRangeTransform().decompose(aScale, aTranslate, fRotate, fShearX);
1129
1130 // prepare outliner
1131 SolarMutexGuard aSolarGuard;
1132 SdrOutliner& rOutliner = ImpGetDrawOutliner();
1133 const EEControlBits nOriginalControlWord(rOutliner.GetControlWord());
1134 const Size aNullSize;
1135
1136 rOutliner.SetControlWord(nOriginalControlWord|EEControlBits::STRETCHING|EEControlBits::AUTOPAGESIZE);
1137 rOutliner.SetFixedCellHeight(rSdrStretchTextPrimitive.isFixedCellHeight());
1138 rOutliner.SetMinAutoPaperSize(aNullSize);
1139 rOutliner.SetMaxAutoPaperSize(Size(1000000,1000000));
1140 rOutliner.SetPaperSize(aNullSize);
1141 rOutliner.SetUpdateMode(true);
1142 rOutliner.SetText(rSdrStretchTextPrimitive.getOutlinerParaObject());
1143
1144 // set visualizing page at Outliner; needed e.g. for PageNumberField decomposition
1145 rOutliner.setVisualizedPage(GetSdrPageFromXDrawPage(aViewInformation.getVisualizedPage()));
1146
1147 // now get back the laid out text size from outliner
1148 const Size aOutlinerTextSize(rOutliner.CalcTextSize());
1149 const basegfx::B2DVector aOutlinerScale(
1150 basegfx::fTools::equalZero(aOutlinerTextSize.Width()) ? 1.0 : aOutlinerTextSize.Width(),
1151 basegfx::fTools::equalZero(aOutlinerTextSize.Height()) ? 1.0 : aOutlinerTextSize.Height());
1152
1153 // prepare matrices to apply to newly created primitives
1154 basegfx::B2DHomMatrix aNewTransformA;
1155
1156 // #i101957# Check for vertical text. If used, aNewTransformA
1157 // needs to translate the text initially around object width to orient
1158 // it relative to the topper right instead of the topper left
1159 const bool bVertical(rSdrStretchTextPrimitive.getOutlinerParaObject().IsVertical());
1160 const bool bTopToBottom(rSdrStretchTextPrimitive.getOutlinerParaObject().IsTopToBottom());
1161
1162 if(bVertical)
1163 {
1164 if(bTopToBottom)
1165 aNewTransformA.translate(aScale.getX(), 0.0);
1166 else
1167 aNewTransformA.translate(0.0, aScale.getY());
1168 }
1169
1170 // calculate global char stretching scale parameters. Use non-mirrored sizes
1171 // to layout without mirroring
1172 const double fScaleX(fabs(aScale.getX()) / aOutlinerScale.getX());
1173 const double fScaleY(fabs(aScale.getY()) / aOutlinerScale.getY());
1174 rOutliner.SetGlobalCharStretching(static_cast<sal_Int16>(FRound(fScaleX * 100.0)), static_cast<sal_Int16>(FRound(fScaleY * 100.0)));
1175
1176 // When mirroring in X and Y,
1177 // move the null point which was top left to bottom right.
1178 const bool bMirrorX(basegfx::fTools::less(aScale.getX(), 0.0));
1179 const bool bMirrorY(basegfx::fTools::less(aScale.getY(), 0.0));
1180
1181 // in-between the translations of the single primitives will take place. Afterwards,
1182 // the object's transformations need to be applied
1183 const basegfx::B2DHomMatrix aNewTransformB(basegfx::utils::createScaleShearXRotateTranslateB2DHomMatrix(
1184 bMirrorX ? -1.0 : 1.0, bMirrorY ? -1.0 : 1.0,
1185 fShearX, fRotate, aTranslate.getX(), aTranslate.getY()));
1186
1187 // now break up text primitives.
1188 impTextBreakupHandler aConverter(rOutliner);
1189 aConverter.decomposeStretchTextPrimitive(aNewTransformA, aNewTransformB);
1190
1191 // cleanup outliner
1192 rOutliner.SetControlWord(nOriginalControlWord);
1193 rOutliner.Clear();
1194 rOutliner.setVisualizedPage(nullptr);
1195
1196 rTarget = aConverter.getPrimitive2DSequence();
1197 }
1198
1199
1200 // timing generators
1201 #define ENDLESS_LOOP (0xffffffff)
1202 #define ENDLESS_TIME (double(0xffffffff))
1203 #define PIXEL_DPI (96.0)
1204
impGetBlinkTextTiming(drawinglayer::animation::AnimationEntryList & rAnimList) const1205 void SdrTextObj::impGetBlinkTextTiming(drawinglayer::animation::AnimationEntryList& rAnimList) const
1206 {
1207 if(SdrTextAniKind::Blink != GetTextAniKind())
1208 return;
1209
1210 // get values
1211 const SfxItemSet& rSet = GetObjectItemSet();
1212 const sal_uInt32 nRepeat(static_cast<sal_uInt32>(rSet.Get(SDRATTR_TEXT_ANICOUNT).GetValue()));
1213 double fDelay(static_cast<double>(rSet.Get(SDRATTR_TEXT_ANIDELAY).GetValue()));
1214
1215 if(0.0 == fDelay)
1216 {
1217 // use default
1218 fDelay = 250.0;
1219 }
1220
1221 // prepare loop and add
1222 drawinglayer::animation::AnimationEntryLoop aLoop(nRepeat ? nRepeat : ENDLESS_LOOP);
1223 drawinglayer::animation::AnimationEntryFixed aStart(fDelay, 0.0);
1224 aLoop.append(aStart);
1225 drawinglayer::animation::AnimationEntryFixed aEnd(fDelay, 1.0);
1226 aLoop.append(aEnd);
1227 rAnimList.append(aLoop);
1228
1229 // add stopped state if loop is not endless
1230 if(0L != nRepeat)
1231 {
1232 bool bVisibleWhenStopped(rSet.Get(SDRATTR_TEXT_ANISTOPINSIDE).GetValue());
1233 drawinglayer::animation::AnimationEntryFixed aStop(ENDLESS_TIME, bVisibleWhenStopped ? 0.0 : 1.0);
1234 rAnimList.append(aStop);
1235 }
1236 }
1237
impCreateScrollTiming(const SfxItemSet & rSet,drawinglayer::animation::AnimationEntryList & rAnimList,bool bForward,double fTimeFullPath,double fFrequency)1238 static void impCreateScrollTiming(const SfxItemSet& rSet, drawinglayer::animation::AnimationEntryList& rAnimList, bool bForward, double fTimeFullPath, double fFrequency)
1239 {
1240 bool bVisibleWhenStopped(rSet.Get(SDRATTR_TEXT_ANISTOPINSIDE).GetValue());
1241 bool bVisibleWhenStarted(rSet.Get(SDRATTR_TEXT_ANISTARTINSIDE).GetValue());
1242 const sal_uInt32 nRepeat(rSet.Get(SDRATTR_TEXT_ANICOUNT).GetValue());
1243
1244 if(bVisibleWhenStarted)
1245 {
1246 // move from center to outside
1247 drawinglayer::animation::AnimationEntryLinear aInOut(fTimeFullPath * 0.5, fFrequency, 0.5, bForward ? 1.0 : 0.0);
1248 rAnimList.append(aInOut);
1249 }
1250
1251 // loop. In loop, move through
1252 drawinglayer::animation::AnimationEntryLoop aLoop(nRepeat ? nRepeat : ENDLESS_LOOP);
1253 drawinglayer::animation::AnimationEntryLinear aThrough(fTimeFullPath, fFrequency, bForward ? 0.0 : 1.0, bForward ? 1.0 : 0.0);
1254 aLoop.append(aThrough);
1255 rAnimList.append(aLoop);
1256
1257 if(0L != nRepeat && bVisibleWhenStopped)
1258 {
1259 // move from outside to center
1260 drawinglayer::animation::AnimationEntryLinear aOutIn(fTimeFullPath * 0.5, fFrequency, bForward ? 0.0 : 1.0, 0.5);
1261 rAnimList.append(aOutIn);
1262
1263 // add timing for staying at the end
1264 drawinglayer::animation::AnimationEntryFixed aEnd(ENDLESS_TIME, 0.5);
1265 rAnimList.append(aEnd);
1266 }
1267 }
1268
impCreateAlternateTiming(const SfxItemSet & rSet,drawinglayer::animation::AnimationEntryList & rAnimList,double fRelativeTextLength,bool bForward,double fTimeFullPath,double fFrequency)1269 static void impCreateAlternateTiming(const SfxItemSet& rSet, drawinglayer::animation::AnimationEntryList& rAnimList, double fRelativeTextLength, bool bForward, double fTimeFullPath, double fFrequency)
1270 {
1271 if(basegfx::fTools::more(fRelativeTextLength, 0.5))
1272 {
1273 // this is the case when fTextLength > fFrameLength, text is bigger than animation frame.
1274 // In that case, correct direction
1275 bForward = !bForward;
1276 }
1277
1278 const double fStartPosition(bForward ? fRelativeTextLength : 1.0 - fRelativeTextLength);
1279 const double fEndPosition(bForward ? 1.0 - fRelativeTextLength : fRelativeTextLength);
1280 bool bVisibleWhenStarted(rSet.Get(SDRATTR_TEXT_ANISTARTINSIDE).GetValue());
1281 const sal_uInt32 nRepeat(rSet.Get(SDRATTR_TEXT_ANICOUNT).GetValue());
1282
1283 if(!bVisibleWhenStarted)
1284 {
1285 // move from outside to center
1286 drawinglayer::animation::AnimationEntryLinear aOutIn(fTimeFullPath * 0.5, fFrequency, bForward ? 0.0 : 1.0, 0.5);
1287 rAnimList.append(aOutIn);
1288 }
1289
1290 // loop. In loop, move out and in again. fInnerMovePath may be negative when text is bigger then frame,
1291 // so use absolute value
1292 const double fInnerMovePath(fabs(1.0 - (fRelativeTextLength * 2.0)));
1293 const double fTimeForInnerPath(fTimeFullPath * fInnerMovePath);
1294 const double fHalfInnerPath(fTimeForInnerPath * 0.5);
1295 const sal_uInt32 nDoubleRepeat(nRepeat / 2L);
1296
1297 if(nDoubleRepeat || 0 == nRepeat)
1298 {
1299 // double forth and back loop
1300 drawinglayer::animation::AnimationEntryLoop aLoop(nDoubleRepeat ? nDoubleRepeat : ENDLESS_LOOP);
1301 drawinglayer::animation::AnimationEntryLinear aTime0(fHalfInnerPath, fFrequency, 0.5, fEndPosition);
1302 aLoop.append(aTime0);
1303 drawinglayer::animation::AnimationEntryLinear aTime1(fTimeForInnerPath, fFrequency, fEndPosition, fStartPosition);
1304 aLoop.append(aTime1);
1305 drawinglayer::animation::AnimationEntryLinear aTime2(fHalfInnerPath, fFrequency, fStartPosition, 0.5);
1306 aLoop.append(aTime2);
1307 rAnimList.append(aLoop);
1308 }
1309
1310 if(nRepeat % 2L)
1311 {
1312 // repeat is uneven, so we need one more forth and back to center
1313 drawinglayer::animation::AnimationEntryLinear aTime0(fHalfInnerPath, fFrequency, 0.5, fEndPosition);
1314 rAnimList.append(aTime0);
1315 drawinglayer::animation::AnimationEntryLinear aTime1(fHalfInnerPath, fFrequency, fEndPosition, 0.5);
1316 rAnimList.append(aTime1);
1317 }
1318
1319 if(0L != nRepeat)
1320 {
1321 bool bVisibleWhenStopped(rSet.Get(SDRATTR_TEXT_ANISTOPINSIDE).GetValue());
1322 if(bVisibleWhenStopped)
1323 {
1324 // add timing for staying at the end
1325 drawinglayer::animation::AnimationEntryFixed aEnd(ENDLESS_TIME, 0.5);
1326 rAnimList.append(aEnd);
1327 }
1328 else
1329 {
1330 // move from center to outside
1331 drawinglayer::animation::AnimationEntryLinear aInOut(fTimeFullPath * 0.5, fFrequency, 0.5, bForward ? 1.0 : 0.0);
1332 rAnimList.append(aInOut);
1333 }
1334 }
1335 }
1336
impCreateSlideTiming(const SfxItemSet & rSet,drawinglayer::animation::AnimationEntryList & rAnimList,bool bForward,double fTimeFullPath,double fFrequency)1337 static void impCreateSlideTiming(const SfxItemSet& rSet, drawinglayer::animation::AnimationEntryList& rAnimList, bool bForward, double fTimeFullPath, double fFrequency)
1338 {
1339 // move in from outside, start outside
1340 const double fStartPosition(bForward ? 0.0 : 1.0);
1341 const sal_uInt32 nRepeat(rSet.Get(SDRATTR_TEXT_ANICOUNT).GetValue());
1342
1343 // move from outside to center
1344 drawinglayer::animation::AnimationEntryLinear aOutIn(fTimeFullPath * 0.5, fFrequency, fStartPosition, 0.5);
1345 rAnimList.append(aOutIn);
1346
1347 // loop. In loop, move out and in again
1348 if(nRepeat > 1 || 0 == nRepeat)
1349 {
1350 drawinglayer::animation::AnimationEntryLoop aLoop(nRepeat ? nRepeat - 1 : ENDLESS_LOOP);
1351 drawinglayer::animation::AnimationEntryLinear aTime0(fTimeFullPath * 0.5, fFrequency, 0.5, fStartPosition);
1352 aLoop.append(aTime0);
1353 drawinglayer::animation::AnimationEntryLinear aTime1(fTimeFullPath * 0.5, fFrequency, fStartPosition, 0.5);
1354 aLoop.append(aTime1);
1355 rAnimList.append(aLoop);
1356 }
1357
1358 // always visible when stopped, so add timing for staying at the end when not endless
1359 if(0L != nRepeat)
1360 {
1361 drawinglayer::animation::AnimationEntryFixed aEnd(ENDLESS_TIME, 0.5);
1362 rAnimList.append(aEnd);
1363 }
1364 }
1365
impGetScrollTextTiming(drawinglayer::animation::AnimationEntryList & rAnimList,double fFrameLength,double fTextLength) const1366 void SdrTextObj::impGetScrollTextTiming(drawinglayer::animation::AnimationEntryList& rAnimList, double fFrameLength, double fTextLength) const
1367 {
1368 const SdrTextAniKind eAniKind(GetTextAniKind());
1369
1370 if(SdrTextAniKind::Scroll != eAniKind && SdrTextAniKind::Alternate != eAniKind && SdrTextAniKind::Slide != eAniKind)
1371 return;
1372
1373 // get data. Goal is to calculate fTimeFullPath which is the time needed to
1374 // move animation from (0.0) to (1.0) state
1375 const SfxItemSet& rSet = GetObjectItemSet();
1376 double fAnimationDelay(static_cast<double>(rSet.Get(SDRATTR_TEXT_ANIDELAY).GetValue()));
1377 double fSingleStepWidth(static_cast<double>(rSet.Get(SDRATTR_TEXT_ANIAMOUNT).GetValue()));
1378 const SdrTextAniDirection eDirection(GetTextAniDirection());
1379 const bool bForward(SdrTextAniDirection::Right == eDirection || SdrTextAniDirection::Down == eDirection);
1380
1381 if(basegfx::fTools::equalZero(fAnimationDelay))
1382 {
1383 // default to 1/20 second
1384 fAnimationDelay = 50.0;
1385 }
1386
1387 if(basegfx::fTools::less(fSingleStepWidth, 0.0))
1388 {
1389 // data is in pixels, convert to logic. Imply PIXEL_DPI dpi.
1390 // It makes no sense to keep the view-transformation centered
1391 // definitions, so get rid of them here.
1392 fSingleStepWidth = (-fSingleStepWidth * (2540.0 / PIXEL_DPI));
1393 }
1394
1395 if(basegfx::fTools::equalZero(fSingleStepWidth))
1396 {
1397 // default to 1 millimeter
1398 fSingleStepWidth = 100.0;
1399 }
1400
1401 // use the length of the full animation path and the number of steps
1402 // to get the full path time
1403 const double fFullPathLength(fFrameLength + fTextLength);
1404 const double fNumberOfSteps(fFullPathLength / fSingleStepWidth);
1405 double fTimeFullPath(fNumberOfSteps * fAnimationDelay);
1406
1407 if(fTimeFullPath < fAnimationDelay)
1408 {
1409 fTimeFullPath = fAnimationDelay;
1410 }
1411
1412 switch(eAniKind)
1413 {
1414 case SdrTextAniKind::Scroll :
1415 {
1416 impCreateScrollTiming(rSet, rAnimList, bForward, fTimeFullPath, fAnimationDelay);
1417 break;
1418 }
1419 case SdrTextAniKind::Alternate :
1420 {
1421 double fRelativeTextLength(fTextLength / (fFrameLength + fTextLength));
1422 impCreateAlternateTiming(rSet, rAnimList, fRelativeTextLength, bForward, fTimeFullPath, fAnimationDelay);
1423 break;
1424 }
1425 case SdrTextAniKind::Slide :
1426 {
1427 impCreateSlideTiming(rSet, rAnimList, bForward, fTimeFullPath, fAnimationDelay);
1428 break;
1429 }
1430 default : break; // SdrTextAniKind::NONE, SdrTextAniKind::Blink
1431 }
1432 }
1433
impHandleChainingEventsDuringDecomposition(SdrOutliner & rOutliner) const1434 void SdrTextObj::impHandleChainingEventsDuringDecomposition(SdrOutliner &rOutliner) const
1435 {
1436 if (GetTextChain()->GetNilChainingEvent(this))
1437 return;
1438
1439 GetTextChain()->SetNilChainingEvent(this, true);
1440
1441 TextChainFlow aTxtChainFlow(const_cast<SdrTextObj*>(this));
1442 bool bIsOverflow;
1443
1444 #ifdef DBG_UTIL
1445 // Some debug output
1446 size_t nObjCount(getSdrPageFromSdrObject()->GetObjCount());
1447 for (size_t i = 0; i < nObjCount; i++)
1448 {
1449 SdrTextObj* pCurObj(dynamic_cast< SdrTextObj* >(getSdrPageFromSdrObject()->GetObj(i)));
1450 if(pCurObj == this)
1451 {
1452 SAL_INFO("svx.chaining", "Working on TextBox " << i);
1453 break;
1454 }
1455 }
1456 #endif
1457
1458 aTxtChainFlow.CheckForFlowEvents(&rOutliner);
1459
1460 if (aTxtChainFlow.IsUnderflow() && !IsInEditMode())
1461 {
1462 // underflow-induced overflow
1463 aTxtChainFlow.ExecuteUnderflow(&rOutliner);
1464 bIsOverflow = aTxtChainFlow.IsOverflow();
1465 } else {
1466 // standard overflow (no underflow before)
1467 bIsOverflow = aTxtChainFlow.IsOverflow();
1468 }
1469
1470 if (bIsOverflow && !IsInEditMode()) {
1471 // Initialize Chaining Outliner
1472 SdrOutliner &rChainingOutl(getSdrModelFromSdrObject().GetChainingOutliner(this));
1473 ImpInitDrawOutliner( rChainingOutl );
1474 rChainingOutl.SetUpdateMode(true);
1475 // We must pass the chaining outliner otherwise we would mess up decomposition
1476 aTxtChainFlow.ExecuteOverflow(&rOutliner, &rChainingOutl);
1477 }
1478
1479 GetTextChain()->SetNilChainingEvent(this, false);
1480 }
1481
impDecomposeChainedTextPrimitive(drawinglayer::primitive2d::Primitive2DContainer & rTarget,const drawinglayer::primitive2d::SdrChainedTextPrimitive2D & rSdrChainedTextPrimitive,const drawinglayer::geometry::ViewInformation2D & aViewInformation) const1482 void SdrTextObj::impDecomposeChainedTextPrimitive(
1483 drawinglayer::primitive2d::Primitive2DContainer& rTarget,
1484 const drawinglayer::primitive2d::SdrChainedTextPrimitive2D& rSdrChainedTextPrimitive,
1485 const drawinglayer::geometry::ViewInformation2D& aViewInformation) const
1486 {
1487 // decompose matrix to have position and size of text
1488 basegfx::B2DVector aScale, aTranslate;
1489 double fRotate, fShearX;
1490 rSdrChainedTextPrimitive.getTextRangeTransform().decompose(aScale, aTranslate, fRotate, fShearX);
1491
1492 // use B2DRange aAnchorTextRange for calculations
1493 basegfx::B2DRange aAnchorTextRange(aTranslate);
1494 aAnchorTextRange.expand(aTranslate + aScale);
1495
1496 // prepare outliner
1497 const SfxItemSet& rTextItemSet = rSdrChainedTextPrimitive.getSdrText()->GetItemSet();
1498 SolarMutexGuard aSolarGuard;
1499 SdrOutliner& rOutliner = ImpGetDrawOutliner();
1500
1501 SdrTextVertAdjust eVAdj = GetTextVerticalAdjust(rTextItemSet);
1502 SdrTextHorzAdjust eHAdj = GetTextHorizontalAdjust(rTextItemSet);
1503 const EEControlBits nOriginalControlWord(rOutliner.GetControlWord());
1504 const Size aNullSize;
1505
1506 // set visualizing page at Outliner; needed e.g. for PageNumberField decomposition
1507 rOutliner.setVisualizedPage(GetSdrPageFromXDrawPage(aViewInformation.getVisualizedPage()));
1508
1509 rOutliner.SetControlWord(nOriginalControlWord|EEControlBits::AUTOPAGESIZE|EEControlBits::STRETCHING);
1510 rOutliner.SetMinAutoPaperSize(aNullSize);
1511 rOutliner.SetMaxAutoPaperSize(Size(1000000,1000000));
1512
1513 // add one to rage sizes to get back to the old Rectangle and outliner measurements
1514 const sal_uInt32 nAnchorTextWidth(FRound(aAnchorTextRange.getWidth() + 1));
1515 const sal_uInt32 nAnchorTextHeight(FRound(aAnchorTextRange.getHeight() + 1));
1516
1517 // Text
1518 const OutlinerParaObject* pOutlinerParaObject = rSdrChainedTextPrimitive.getSdrText()->GetOutlinerParaObject();
1519 OSL_ENSURE(pOutlinerParaObject, "impDecomposeBlockTextPrimitive used with no OutlinerParaObject (!)");
1520
1521 const bool bVerticalWriting(pOutlinerParaObject->IsVertical());
1522 const bool bTopToBottom(pOutlinerParaObject->IsTopToBottom());
1523 const Size aAnchorTextSize(Size(nAnchorTextWidth, nAnchorTextHeight));
1524
1525 if(IsTextFrame())
1526 {
1527 rOutliner.SetMaxAutoPaperSize(aAnchorTextSize);
1528 }
1529
1530 if(SDRTEXTHORZADJUST_BLOCK == eHAdj && !bVerticalWriting)
1531 {
1532 rOutliner.SetMinAutoPaperSize(Size(nAnchorTextWidth, 0));
1533 }
1534
1535 if(SDRTEXTVERTADJUST_BLOCK == eVAdj && bVerticalWriting)
1536 {
1537 rOutliner.SetMinAutoPaperSize(Size(0, nAnchorTextHeight));
1538 }
1539
1540 rOutliner.SetPaperSize(aNullSize);
1541 rOutliner.SetUpdateMode(true);
1542 // Sets original text
1543 rOutliner.SetText(*pOutlinerParaObject);
1544
1545 /* Begin overflow/underflow handling */
1546
1547 impHandleChainingEventsDuringDecomposition(rOutliner);
1548
1549 /* End overflow/underflow handling */
1550
1551 // set visualizing page at Outliner; needed e.g. for PageNumberField decomposition
1552 rOutliner.setVisualizedPage(GetSdrPageFromXDrawPage(aViewInformation.getVisualizedPage()));
1553
1554 // now get back the layouted text size from outliner
1555 const Size aOutlinerTextSize(rOutliner.GetPaperSize());
1556 const basegfx::B2DVector aOutlinerScale(aOutlinerTextSize.Width(), aOutlinerTextSize.Height());
1557 basegfx::B2DVector aAdjustTranslate(0.0, 0.0);
1558
1559 // correct horizontal translation using the now known text size
1560 if(SDRTEXTHORZADJUST_CENTER == eHAdj || SDRTEXTHORZADJUST_RIGHT == eHAdj)
1561 {
1562 const double fFree(aAnchorTextRange.getWidth() - aOutlinerScale.getX());
1563
1564 if(SDRTEXTHORZADJUST_CENTER == eHAdj)
1565 {
1566 aAdjustTranslate.setX(fFree / 2.0);
1567 }
1568
1569 if(SDRTEXTHORZADJUST_RIGHT == eHAdj)
1570 {
1571 aAdjustTranslate.setX(fFree);
1572 }
1573 }
1574
1575 // correct vertical translation using the now known text size
1576 if(SDRTEXTVERTADJUST_CENTER == eVAdj || SDRTEXTVERTADJUST_BOTTOM == eVAdj)
1577 {
1578 const double fFree(aAnchorTextRange.getHeight() - aOutlinerScale.getY());
1579
1580 if(SDRTEXTVERTADJUST_CENTER == eVAdj)
1581 {
1582 aAdjustTranslate.setY(fFree / 2.0);
1583 }
1584
1585 if(SDRTEXTVERTADJUST_BOTTOM == eVAdj)
1586 {
1587 aAdjustTranslate.setY(fFree);
1588 }
1589 }
1590
1591 // prepare matrices to apply to newly created primitives. aNewTransformA
1592 // will get coordinates in aOutlinerScale size and positive in X, Y.
1593 basegfx::B2DHomMatrix aNewTransformA;
1594 basegfx::B2DHomMatrix aNewTransformB;
1595
1596 // translate relative to given primitive to get same rotation and shear
1597 // as the master shape we are working on. For vertical, use the top-right
1598 // corner
1599 const double fStartInX(bVerticalWriting && bTopToBottom ? aAdjustTranslate.getX() + aOutlinerScale.getX() : aAdjustTranslate.getX());
1600 const double fStartInY(bVerticalWriting && !bTopToBottom ? aAdjustTranslate.getY() + aOutlinerScale.getY() : aAdjustTranslate.getY());
1601 aNewTransformA.translate(fStartInX, fStartInY);
1602
1603 // mirroring. We are now in aAnchorTextRange sizes. When mirroring in X and Y,
1604 // move the null point which was top left to bottom right.
1605 const bool bMirrorX(basegfx::fTools::less(aScale.getX(), 0.0));
1606 const bool bMirrorY(basegfx::fTools::less(aScale.getY(), 0.0));
1607 aNewTransformB.scale(bMirrorX ? -1.0 : 1.0, bMirrorY ? -1.0 : 1.0);
1608
1609 // in-between the translations of the single primitives will take place. Afterwards,
1610 // the object's transformations need to be applied
1611 aNewTransformB.shearX(fShearX);
1612 aNewTransformB.rotate(fRotate);
1613 aNewTransformB.translate(aTranslate.getX(), aTranslate.getY());
1614
1615 basegfx::B2DRange aClipRange;
1616
1617 // now break up text primitives.
1618 impTextBreakupHandler aConverter(rOutliner);
1619 aConverter.decomposeBlockTextPrimitive(aNewTransformA, aNewTransformB, aClipRange);
1620
1621 // cleanup outliner
1622 rOutliner.Clear();
1623 rOutliner.setVisualizedPage(nullptr);
1624 rOutliner.SetControlWord(nOriginalControlWord);
1625
1626 rTarget = aConverter.getPrimitive2DSequence();
1627 }
1628
1629 // Direct decomposer for text visualization when you already have a prepared
1630 // Outliner containing all the needed information
impDecomposeBlockTextPrimitiveDirect(drawinglayer::primitive2d::Primitive2DContainer & rTarget,SdrOutliner & rOutliner,const basegfx::B2DHomMatrix & rNewTransformA,const basegfx::B2DHomMatrix & rNewTransformB,const basegfx::B2DRange & rClipRange)1631 void SdrTextObj::impDecomposeBlockTextPrimitiveDirect(
1632 drawinglayer::primitive2d::Primitive2DContainer& rTarget,
1633 SdrOutliner& rOutliner,
1634 const basegfx::B2DHomMatrix& rNewTransformA,
1635 const basegfx::B2DHomMatrix& rNewTransformB,
1636 const basegfx::B2DRange& rClipRange)
1637 {
1638 impTextBreakupHandler aConverter(rOutliner);
1639 aConverter.decomposeBlockTextPrimitive(rNewTransformA, rNewTransformB, rClipRange);
1640 rTarget.append(aConverter.getPrimitive2DSequence());
1641 }
1642
1643 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */
1644