1 /**************************************************************
2 *
3 * Licensed to the Apache Software Foundation (ASF) under one
4 * or more contributor license agreements. See the NOTICE file
5 * distributed with this work for additional information
6 * regarding copyright ownership. The ASF licenses this file
7 * to you under the Apache License, Version 2.0 (the
8 * "License"); you may not use this file except in compliance
9 * with the License. You may obtain a copy of the License at
10 *
11 * http://www.apache.org/licenses/LICENSE-2.0
12 *
13 * Unless required by applicable law or agreed to in writing,
14 * software distributed under the License is distributed on an
15 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
16 * KIND, either express or implied. See the License for the
17 * specific language governing permissions and limitations
18 * under the License.
19 *
20 *************************************************************/
21
22
23
24 // MARKER(update_precomp.py): autogen include statement, do not remove
25 #include "precompiled_svx.hxx"
26
27 #include <svx/svdotext.hxx>
28 #include <svx/svdoutl.hxx>
29 #include <basegfx/vector/b2dvector.hxx>
30 #include <svx/sdr/primitive2d/sdrtextprimitive2d.hxx>
31 #include <drawinglayer/primitive2d/textprimitive2d.hxx>
32 #include <drawinglayer/primitive2d/textdecoratedprimitive2d.hxx>
33 #include <basegfx/range/b2drange.hxx>
34 #include <editeng/editstat.hxx>
35 #include <vcl/salbtype.hxx>
36 #include <svx/sdtfchim.hxx>
37 #include <svl/itemset.hxx>
38 #include <basegfx/polygon/b2dpolygontools.hxx>
39 #include <basegfx/polygon/b2dpolygon.hxx>
40 #include <drawinglayer/animation/animationtiming.hxx>
41 #include <basegfx/color/bcolor.hxx>
42 #include <vcl/svapp.hxx>
43 #include <editeng/eeitemid.hxx>
44 #include <editeng/escpitem.hxx>
45 #include <editeng/svxenum.hxx>
46 #include <editeng/flditem.hxx>
47 #include <drawinglayer/primitive2d/texthierarchyprimitive2d.hxx>
48 #include <vcl/metaact.hxx>
49 #include <drawinglayer/primitive2d/wrongspellprimitive2d.hxx>
50 #include <drawinglayer/primitive2d/graphicprimitive2d.hxx>
51 #include <drawinglayer/primitive2d/textlayoutdevice.hxx>
52 #include <svx/unoapi.hxx>
53 #include <drawinglayer/geometry/viewinformation2d.hxx>
54 #include <editeng/outlobj.hxx>
55 #include <basegfx/matrix/b2dhommatrixtools.hxx>
56
57 //////////////////////////////////////////////////////////////////////////////
58 // helpers
59
60 namespace
61 {
impConvertVectorToPrimitive2DSequence(const std::vector<drawinglayer::primitive2d::BasePrimitive2D * > & rPrimitiveVector)62 drawinglayer::primitive2d::Primitive2DSequence impConvertVectorToPrimitive2DSequence(const std::vector< drawinglayer::primitive2d::BasePrimitive2D* >& rPrimitiveVector)
63 {
64 const sal_Int32 nCount(rPrimitiveVector.size());
65 drawinglayer::primitive2d::Primitive2DSequence aRetval(nCount);
66
67 for(sal_Int32 a(0L); a < nCount; a++)
68 {
69 aRetval[a] = drawinglayer::primitive2d::Primitive2DReference(rPrimitiveVector[a]);
70 }
71
72 return aRetval;
73 }
74
75 class impTextBreakupHandler
76 {
77 private:
78 std::vector< drawinglayer::primitive2d::BasePrimitive2D* > maTextPortionPrimitives;
79 std::vector< drawinglayer::primitive2d::BasePrimitive2D* > maLinePrimitives;
80 std::vector< drawinglayer::primitive2d::BasePrimitive2D* > maParagraphPrimitives;
81
82 SdrOutliner& mrOutliner;
83 basegfx::B2DHomMatrix maNewTransformA;
84 basegfx::B2DHomMatrix maNewTransformB;
85
86 // the visible area for contour text decomposition
87 basegfx::B2DVector maScale;
88
89 // #SJ# ClipRange for BlockText decomposition; only text portions completely
90 // inside are to be accepted, so this is different from geometric clipping
91 // (which would allow e.g. upper parts of portions to remain). Only used for
92 // BlockText (see there)
93 basegfx::B2DRange maClipRange;
94
95 DECL_LINK(decomposeContourTextPrimitive, DrawPortionInfo* );
96 DECL_LINK(decomposeBlockTextPrimitive, DrawPortionInfo* );
97 DECL_LINK(decomposeStretchTextPrimitive, DrawPortionInfo* );
98
99 DECL_LINK(decomposeContourBulletPrimitive, DrawBulletInfo* );
100 DECL_LINK(decomposeBlockBulletPrimitive, DrawBulletInfo* );
101 DECL_LINK(decomposeStretchBulletPrimitive, DrawBulletInfo* );
102
103 bool impIsUnderlineAbove(const Font& rFont) const;
104 void impCreateTextPortionPrimitive(const DrawPortionInfo& rInfo);
105 drawinglayer::primitive2d::BasePrimitive2D* impCheckFieldPrimitive(drawinglayer::primitive2d::BasePrimitive2D* pPrimitive, const DrawPortionInfo& rInfo) const;
106 void impFlushTextPortionPrimitivesToLinePrimitives();
107 void impFlushLinePrimitivesToParagraphPrimitives();
108 void impHandleDrawPortionInfo(const DrawPortionInfo& rInfo);
109 void impHandleDrawBulletInfo(const DrawBulletInfo& rInfo);
110
111 public:
impTextBreakupHandler(SdrOutliner & rOutliner)112 impTextBreakupHandler(SdrOutliner& rOutliner)
113 : maTextPortionPrimitives(),
114 maLinePrimitives(),
115 maParagraphPrimitives(),
116 mrOutliner(rOutliner),
117 maNewTransformA(),
118 maNewTransformB(),
119 maScale(),
120 maClipRange()
121 {
122 }
123
decomposeContourTextPrimitive(const basegfx::B2DHomMatrix & rNewTransformA,const basegfx::B2DHomMatrix & rNewTransformB,const basegfx::B2DVector & rScale)124 void decomposeContourTextPrimitive(const basegfx::B2DHomMatrix& rNewTransformA, const basegfx::B2DHomMatrix& rNewTransformB, const basegfx::B2DVector& rScale)
125 {
126 maScale = rScale;
127 maNewTransformA = rNewTransformA;
128 maNewTransformB = rNewTransformB;
129 mrOutliner.SetDrawPortionHdl(LINK(this, impTextBreakupHandler, decomposeContourTextPrimitive));
130 mrOutliner.SetDrawBulletHdl(LINK(this, impTextBreakupHandler, decomposeContourBulletPrimitive));
131 mrOutliner.StripPortions();
132 mrOutliner.SetDrawPortionHdl(Link());
133 mrOutliner.SetDrawBulletHdl(Link());
134 }
135
decomposeBlockTextPrimitive(const basegfx::B2DHomMatrix & rNewTransformA,const basegfx::B2DHomMatrix & rNewTransformB,const basegfx::B2DRange & rClipRange)136 void decomposeBlockTextPrimitive(
137 const basegfx::B2DHomMatrix& rNewTransformA,
138 const basegfx::B2DHomMatrix& rNewTransformB,
139 const basegfx::B2DRange& rClipRange)
140 {
141 maNewTransformA = rNewTransformA;
142 maNewTransformB = rNewTransformB;
143 maClipRange = rClipRange;
144 mrOutliner.SetDrawPortionHdl(LINK(this, impTextBreakupHandler, decomposeBlockTextPrimitive));
145 mrOutliner.SetDrawBulletHdl(LINK(this, impTextBreakupHandler, decomposeBlockBulletPrimitive));
146 mrOutliner.StripPortions();
147 mrOutliner.SetDrawPortionHdl(Link());
148 mrOutliner.SetDrawBulletHdl(Link());
149 }
150
decomposeStretchTextPrimitive(const basegfx::B2DHomMatrix & rNewTransformA,const basegfx::B2DHomMatrix & rNewTransformB)151 void decomposeStretchTextPrimitive(const basegfx::B2DHomMatrix& rNewTransformA, const basegfx::B2DHomMatrix& rNewTransformB)
152 {
153 maNewTransformA = rNewTransformA;
154 maNewTransformB = rNewTransformB;
155 mrOutliner.SetDrawPortionHdl(LINK(this, impTextBreakupHandler, decomposeStretchTextPrimitive));
156 mrOutliner.SetDrawBulletHdl(LINK(this, impTextBreakupHandler, decomposeStretchBulletPrimitive));
157 mrOutliner.StripPortions();
158 mrOutliner.SetDrawPortionHdl(Link());
159 mrOutliner.SetDrawBulletHdl(Link());
160 }
161
162 drawinglayer::primitive2d::Primitive2DSequence getPrimitive2DSequence();
163 };
164
impIsUnderlineAbove(const Font & rFont) const165 bool impTextBreakupHandler::impIsUnderlineAbove(const Font& rFont) const
166 {
167 if(!rFont.IsVertical())
168 {
169 return false;
170 }
171
172 if((LANGUAGE_JAPANESE == rFont.GetLanguage()) || (LANGUAGE_JAPANESE == rFont.GetCJKContextLanguage()))
173 {
174 // the underline is right for Japanese only
175 return true;
176 }
177
178 return false;
179 }
180
impCreateTextPortionPrimitive(const DrawPortionInfo & rInfo)181 void impTextBreakupHandler::impCreateTextPortionPrimitive(const DrawPortionInfo& rInfo)
182 {
183 if(rInfo.mrText.Len() && rInfo.mnTextLen)
184 {
185 basegfx::B2DVector aFontScaling;
186 drawinglayer::attribute::FontAttribute aFontAttribute(
187 drawinglayer::primitive2d::getFontAttributeFromVclFont(
188 aFontScaling,
189 rInfo.mrFont,
190 rInfo.IsRTL(),
191 false));
192 basegfx::B2DHomMatrix aNewTransform;
193
194 // add font scale to new transform
195 aNewTransform.scale(aFontScaling.getX(), aFontScaling.getY());
196
197 // look for proportional font scaling, evtl scale accordingly
198 if(100 != rInfo.mrFont.GetPropr())
199 {
200 const double fFactor(rInfo.mrFont.GetPropr() / 100.0);
201 aNewTransform.scale(fFactor, fFactor);
202 }
203
204 // apply font rotate
205 if(rInfo.mrFont.GetOrientation())
206 {
207 aNewTransform.rotate(-rInfo.mrFont.GetOrientation() * F_PI1800);
208 }
209
210 // look for escapement, evtl translate accordingly
211 if(rInfo.mrFont.GetEscapement())
212 {
213 sal_Int16 nEsc(rInfo.mrFont.GetEscapement());
214
215 if(DFLT_ESC_AUTO_SUPER == nEsc)
216 {
217 nEsc = 33;
218 }
219 else if(DFLT_ESC_AUTO_SUB == nEsc)
220 {
221 nEsc = -20;
222 }
223
224 if(nEsc > 100)
225 {
226 nEsc = 100;
227 }
228 else if(nEsc < -100)
229 {
230 nEsc = -100;
231 }
232
233 const double fEscapement(nEsc / -100.0);
234 aNewTransform.translate(0.0, fEscapement * aFontScaling.getY());
235 }
236
237 // apply transformA
238 aNewTransform *= maNewTransformA;
239
240 // apply local offset
241 aNewTransform.translate(rInfo.mrStartPos.X(), rInfo.mrStartPos.Y());
242
243 // also apply embedding object's transform
244 aNewTransform *= maNewTransformB;
245
246 // prepare DXArray content. To make it independent from font size (and such from
247 // the text transformation), scale it to unit coordinates
248 ::std::vector< double > aDXArray;
249 static bool bDisableTextArray(false);
250
251 if(!bDisableTextArray && rInfo.mpDXArray && rInfo.mnTextLen)
252 {
253 aDXArray.reserve(rInfo.mnTextLen);
254
255 for(xub_StrLen a(0); a < rInfo.mnTextLen; a++)
256 {
257 aDXArray.push_back((double)rInfo.mpDXArray[a]);
258 }
259 }
260
261 // create complex text primitive and append
262 const Color aFontColor(rInfo.mrFont.GetColor());
263 const basegfx::BColor aBFontColor(aFontColor.getBColor());
264
265 // prepare wordLineMode (for underline and strikeout)
266 // NOT for bullet texts. It is set (this may be an error by itself), but needs to be suppressed to hinder e.g. '1)'
267 // to be splitted which would not look like the original
268 const bool bWordLineMode(rInfo.mrFont.IsWordLineMode() && !rInfo.mbEndOfBullet);
269
270 // prepare new primitive
271 drawinglayer::primitive2d::BasePrimitive2D* pNewPrimitive = 0;
272 const bool bDecoratedIsNeeded(
273 UNDERLINE_NONE != rInfo.mrFont.GetOverline()
274 || UNDERLINE_NONE != rInfo.mrFont.GetUnderline()
275 || STRIKEOUT_NONE != rInfo.mrFont.GetStrikeout()
276 || EMPHASISMARK_NONE != (rInfo.mrFont.GetEmphasisMark() & EMPHASISMARK_STYLE)
277 || RELIEF_NONE != rInfo.mrFont.GetRelief()
278 || rInfo.mrFont.IsShadow()
279 || bWordLineMode);
280
281 if(bDecoratedIsNeeded)
282 {
283 // TextDecoratedPortionPrimitive2D needed, prepare some more data
284 // get overline and underline color. If it's on automatic (0xffffffff) use FontColor instead
285 const Color aUnderlineColor(rInfo.maTextLineColor);
286 const basegfx::BColor aBUnderlineColor((0xffffffff == aUnderlineColor.GetColor()) ? aBFontColor : aUnderlineColor.getBColor());
287 const Color aOverlineColor(rInfo.maOverlineColor);
288 const basegfx::BColor aBOverlineColor((0xffffffff == aOverlineColor.GetColor()) ? aBFontColor : aOverlineColor.getBColor());
289
290 // prepare overline and underline data
291 const drawinglayer::primitive2d::TextLine eFontOverline(
292 drawinglayer::primitive2d::mapFontUnderlineToTextLine(rInfo.mrFont.GetOverline()));
293 const drawinglayer::primitive2d::TextLine eFontUnderline(
294 drawinglayer::primitive2d::mapFontUnderlineToTextLine(rInfo.mrFont.GetUnderline()));
295
296 // check UndelineAbove
297 const bool bUnderlineAbove(
298 drawinglayer::primitive2d::TEXT_LINE_NONE != eFontUnderline && impIsUnderlineAbove(rInfo.mrFont));
299
300 // prepare strikeout data
301 const drawinglayer::primitive2d::TextStrikeout eTextStrikeout(
302 drawinglayer::primitive2d::mapFontStrikeoutToTextStrikeout(rInfo.mrFont.GetStrikeout()));
303
304 // prepare emphasis mark data
305 drawinglayer::primitive2d::TextEmphasisMark eTextEmphasisMark(drawinglayer::primitive2d::TEXT_EMPHASISMARK_NONE);
306
307 switch(rInfo.mrFont.GetEmphasisMark() & EMPHASISMARK_STYLE)
308 {
309 case EMPHASISMARK_DOT : eTextEmphasisMark = drawinglayer::primitive2d::TEXT_EMPHASISMARK_DOT; break;
310 case EMPHASISMARK_CIRCLE : eTextEmphasisMark = drawinglayer::primitive2d::TEXT_EMPHASISMARK_CIRCLE; break;
311 case EMPHASISMARK_DISC : eTextEmphasisMark = drawinglayer::primitive2d::TEXT_EMPHASISMARK_DISC; break;
312 case EMPHASISMARK_ACCENT : eTextEmphasisMark = drawinglayer::primitive2d::TEXT_EMPHASISMARK_ACCENT; break;
313 }
314
315 const bool bEmphasisMarkAbove(rInfo.mrFont.GetEmphasisMark() & EMPHASISMARK_POS_ABOVE);
316 const bool bEmphasisMarkBelow(rInfo.mrFont.GetEmphasisMark() & EMPHASISMARK_POS_BELOW);
317
318 // prepare font relief data
319 drawinglayer::primitive2d::TextRelief eTextRelief(drawinglayer::primitive2d::TEXT_RELIEF_NONE);
320
321 switch(rInfo.mrFont.GetRelief())
322 {
323 case RELIEF_EMBOSSED : eTextRelief = drawinglayer::primitive2d::TEXT_RELIEF_EMBOSSED; break;
324 case RELIEF_ENGRAVED : eTextRelief = drawinglayer::primitive2d::TEXT_RELIEF_ENGRAVED; break;
325 default : break; // RELIEF_NONE, FontRelief_FORCE_EQUAL_SIZE
326 }
327
328 // prepare shadow/outline data
329 const bool bShadow(rInfo.mrFont.IsShadow());
330
331 // TextDecoratedPortionPrimitive2D is needed, create one
332 pNewPrimitive = new drawinglayer::primitive2d::TextDecoratedPortionPrimitive2D(
333
334 // attributes for TextSimplePortionPrimitive2D
335 aNewTransform,
336 rInfo.mrText,
337 rInfo.mnTextStart,
338 rInfo.mnTextLen,
339 aDXArray,
340 aFontAttribute,
341 rInfo.mpLocale ? *rInfo.mpLocale : ::com::sun::star::lang::Locale(),
342 aBFontColor,
343
344 // attributes for TextDecoratedPortionPrimitive2D
345 aBOverlineColor,
346 aBUnderlineColor,
347 eFontOverline,
348 eFontUnderline,
349 bUnderlineAbove,
350 eTextStrikeout,
351 bWordLineMode,
352 eTextEmphasisMark,
353 bEmphasisMarkAbove,
354 bEmphasisMarkBelow,
355 eTextRelief,
356 bShadow);
357 }
358 else
359 {
360 // TextSimplePortionPrimitive2D is enough
361 pNewPrimitive = new drawinglayer::primitive2d::TextSimplePortionPrimitive2D(
362 aNewTransform,
363 rInfo.mrText,
364 rInfo.mnTextStart,
365 rInfo.mnTextLen,
366 aDXArray,
367 aFontAttribute,
368 rInfo.mpLocale ? *rInfo.mpLocale : ::com::sun::star::lang::Locale(),
369 aBFontColor);
370 }
371
372 if(rInfo.mbEndOfBullet)
373 {
374 // embed in TextHierarchyBulletPrimitive2D
375 const drawinglayer::primitive2d::Primitive2DReference aNewReference(pNewPrimitive);
376 const drawinglayer::primitive2d::Primitive2DSequence aNewSequence(&aNewReference, 1);
377 pNewPrimitive = new drawinglayer::primitive2d::TextHierarchyBulletPrimitive2D(aNewSequence);
378 }
379
380 if(rInfo.mpFieldData)
381 {
382 pNewPrimitive = impCheckFieldPrimitive(pNewPrimitive, rInfo);
383 }
384
385 maTextPortionPrimitives.push_back(pNewPrimitive);
386
387 // support for WrongSpellVector. Create WrongSpellPrimitives as needed
388 if(rInfo.mpWrongSpellVector && !aDXArray.empty())
389 {
390 const sal_uInt32 nSize(rInfo.mpWrongSpellVector->size());
391 const sal_uInt32 nDXCount(aDXArray.size());
392 const basegfx::BColor aSpellColor(1.0, 0.0, 0.0); // red, hard coded
393
394 for(sal_uInt32 a(0); a < nSize; a++)
395 {
396 const EEngineData::WrongSpellClass& rCandidate = (*rInfo.mpWrongSpellVector)[a];
397
398 if(rCandidate.nStart >= rInfo.mnTextStart && rCandidate.nEnd >= rInfo.mnTextStart && rCandidate.nEnd > rCandidate.nStart)
399 {
400 const sal_uInt32 nStart(rCandidate.nStart - rInfo.mnTextStart);
401 const sal_uInt32 nEnd(rCandidate.nEnd - rInfo.mnTextStart);
402 double fStart(0.0);
403 double fEnd(0.0);
404
405 if(nStart > 0 && nStart - 1 < nDXCount)
406 {
407 fStart = aDXArray[nStart - 1];
408 }
409
410 if(nEnd > 0 && nEnd - 1 < nDXCount)
411 {
412 fEnd = aDXArray[nEnd - 1];
413 }
414
415 if(!basegfx::fTools::equal(fStart, fEnd))
416 {
417 if(rInfo.IsRTL())
418 {
419 // #i98523#
420 // When the portion is RTL, mirror the redlining using the
421 // full portion width
422 const double fTextWidth(aDXArray[aDXArray.size() - 1]);
423
424 fStart = fTextWidth - fStart;
425 fEnd = fTextWidth - fEnd;
426 }
427
428 // need to take FontScaling out of values; it's already part of
429 // aNewTransform and would be double applied
430 const double fFontScaleX(aFontScaling.getX());
431
432 if(!basegfx::fTools::equal(fFontScaleX, 1.0)
433 && !basegfx::fTools::equalZero(fFontScaleX))
434 {
435 fStart /= fFontScaleX;
436 fEnd /= fFontScaleX;
437 }
438
439 maTextPortionPrimitives.push_back(new drawinglayer::primitive2d::WrongSpellPrimitive2D(
440 aNewTransform,
441 fStart,
442 fEnd,
443 aSpellColor));
444 }
445 }
446 }
447 }
448 }
449 }
450
impCheckFieldPrimitive(drawinglayer::primitive2d::BasePrimitive2D * pPrimitive,const DrawPortionInfo & rInfo) const451 drawinglayer::primitive2d::BasePrimitive2D* impTextBreakupHandler::impCheckFieldPrimitive(drawinglayer::primitive2d::BasePrimitive2D* pPrimitive, const DrawPortionInfo& rInfo) const
452 {
453 if(rInfo.mpFieldData)
454 {
455 // Support for FIELD_SEQ_BEGIN, FIELD_SEQ_END. If used, create a TextHierarchyFieldPrimitive2D
456 // which holds the field type and evtl. the URL
457 const SvxURLField* pURLField = dynamic_cast< const SvxURLField* >(rInfo.mpFieldData);
458 const SvxPageField* pPageField = dynamic_cast< const SvxPageField* >(rInfo.mpFieldData);
459
460 // embed current primitive to a sequence
461 drawinglayer::primitive2d::Primitive2DSequence aSequence;
462
463 if(pPrimitive)
464 {
465 aSequence.realloc(1);
466 aSequence[0] = drawinglayer::primitive2d::Primitive2DReference(pPrimitive);
467 }
468
469 if(pURLField)
470 {
471 pPrimitive = new drawinglayer::primitive2d::TextHierarchyFieldPrimitive2D(aSequence, drawinglayer::primitive2d::FIELD_TYPE_URL, pURLField->GetURL());
472 }
473 else if(pPageField)
474 {
475 pPrimitive = new drawinglayer::primitive2d::TextHierarchyFieldPrimitive2D(aSequence, drawinglayer::primitive2d::FIELD_TYPE_PAGE, String());
476 }
477 else
478 {
479 pPrimitive = new drawinglayer::primitive2d::TextHierarchyFieldPrimitive2D(aSequence, drawinglayer::primitive2d::FIELD_TYPE_COMMON, String());
480 }
481 }
482
483 return pPrimitive;
484 }
485
impFlushTextPortionPrimitivesToLinePrimitives()486 void impTextBreakupHandler::impFlushTextPortionPrimitivesToLinePrimitives()
487 {
488 // only create a line primitive when we had content; there is no need for
489 // empty line primitives (contrary to paragraphs, see below).
490 if(!maTextPortionPrimitives.empty())
491 {
492 drawinglayer::primitive2d::Primitive2DSequence aLineSequence(impConvertVectorToPrimitive2DSequence(maTextPortionPrimitives));
493 maTextPortionPrimitives.clear();
494 maLinePrimitives.push_back(new drawinglayer::primitive2d::TextHierarchyLinePrimitive2D(aLineSequence));
495 }
496 }
497
impFlushLinePrimitivesToParagraphPrimitives()498 void impTextBreakupHandler::impFlushLinePrimitivesToParagraphPrimitives()
499 {
500 // ALWAYS create a paragraph primitive, even when no content was added. This is done to
501 // have the correct paragraph count even with empty paragraphs. Those paragraphs will
502 // have an empty sub-PrimitiveSequence.
503 drawinglayer::primitive2d::Primitive2DSequence aParagraphSequence(impConvertVectorToPrimitive2DSequence(maLinePrimitives));
504 maLinePrimitives.clear();
505 maParagraphPrimitives.push_back(new drawinglayer::primitive2d::TextHierarchyParagraphPrimitive2D(aParagraphSequence));
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();
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::Primitive2DSequence aNewSequence(&aNewReference, 1);
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)556 IMPL_LINK(impTextBreakupHandler, decomposeContourTextPrimitive, DrawPortionInfo*, pInfo)
557 {
558 // for contour text, ignore (clip away) all portions which are below
559 // the visible area given by maScale
560 if(pInfo && (double)pInfo->mrStartPos.Y() < maScale.getY())
561 {
562 impHandleDrawPortionInfo(*pInfo);
563 }
564
565 return 0;
566 }
567
IMPL_LINK(impTextBreakupHandler,decomposeBlockTextPrimitive,DrawPortionInfo *,pInfo)568 IMPL_LINK(impTextBreakupHandler, decomposeBlockTextPrimitive, DrawPortionInfo*, pInfo)
569 {
570 if(pInfo)
571 {
572 // #SJ# Is clipping wanted? This is text clipping; only accept a portion
573 // if it's completely in the range
574 if(!maClipRange.isEmpty())
575 {
576 // Test start position first; this allows to not get the text range at
577 // all if text is far outside
578 const basegfx::B2DPoint aStartPosition(pInfo->mrStartPos.X(), pInfo->mrStartPos.Y());
579
580 if(!maClipRange.isInside(aStartPosition))
581 {
582 return 0;
583 }
584
585 // Start position is inside. Get TextBoundRect and TopLeft next
586 drawinglayer::primitive2d::TextLayouterDevice aTextLayouterDevice;
587 aTextLayouterDevice.setFont(pInfo->mrFont);
588
589 const basegfx::B2DRange aTextBoundRect(
590 aTextLayouterDevice.getTextBoundRect(
591 pInfo->mrText, pInfo->mnTextStart, pInfo->mnTextLen));
592 const basegfx::B2DPoint aTopLeft(aTextBoundRect.getMinimum() + aStartPosition);
593
594 if(!maClipRange.isInside(aTopLeft))
595 {
596 return 0;
597 }
598
599 // TopLeft is inside. Get BottomRight and check
600 const basegfx::B2DPoint aBottomRight(aTextBoundRect.getMaximum() + aStartPosition);
601
602 if(!maClipRange.isInside(aBottomRight))
603 {
604 return 0;
605 }
606
607 // all inside, clip was successful
608 }
609 impHandleDrawPortionInfo(*pInfo);
610 }
611
612 return 0;
613 }
614
IMPL_LINK(impTextBreakupHandler,decomposeStretchTextPrimitive,DrawPortionInfo *,pInfo)615 IMPL_LINK(impTextBreakupHandler, decomposeStretchTextPrimitive, DrawPortionInfo*, pInfo)
616 {
617 if(pInfo)
618 {
619 impHandleDrawPortionInfo(*pInfo);
620 }
621
622 return 0;
623 }
624
IMPL_LINK(impTextBreakupHandler,decomposeContourBulletPrimitive,DrawBulletInfo *,pInfo)625 IMPL_LINK(impTextBreakupHandler, decomposeContourBulletPrimitive, DrawBulletInfo*, pInfo)
626 {
627 if(pInfo)
628 {
629 impHandleDrawBulletInfo(*pInfo);
630 }
631
632 return 0;
633 }
634
IMPL_LINK(impTextBreakupHandler,decomposeBlockBulletPrimitive,DrawBulletInfo *,pInfo)635 IMPL_LINK(impTextBreakupHandler, decomposeBlockBulletPrimitive, DrawBulletInfo*, pInfo)
636 {
637 if(pInfo)
638 {
639 impHandleDrawBulletInfo(*pInfo);
640 }
641
642 return 0;
643 }
644
IMPL_LINK(impTextBreakupHandler,decomposeStretchBulletPrimitive,DrawBulletInfo *,pInfo)645 IMPL_LINK(impTextBreakupHandler, decomposeStretchBulletPrimitive, DrawBulletInfo*, pInfo)
646 {
647 if(pInfo)
648 {
649 impHandleDrawBulletInfo(*pInfo);
650 }
651
652 return 0;
653 }
654
getPrimitive2DSequence()655 drawinglayer::primitive2d::Primitive2DSequence impTextBreakupHandler::getPrimitive2DSequence()
656 {
657 if(!maTextPortionPrimitives.empty())
658 {
659 // collect non-closed lines
660 impFlushTextPortionPrimitivesToLinePrimitives();
661 }
662
663 if(!maLinePrimitives.empty())
664 {
665 // collect non-closed paragraphs
666 impFlushLinePrimitivesToParagraphPrimitives();
667 }
668
669 return impConvertVectorToPrimitive2DSequence(maParagraphPrimitives);
670 }
671 } // end of anonymous namespace
672
673 //////////////////////////////////////////////////////////////////////////////
674 // primitive decompositions
675
impDecomposeContourTextPrimitive(drawinglayer::primitive2d::Primitive2DSequence & rTarget,const drawinglayer::primitive2d::SdrContourTextPrimitive2D & rSdrContourTextPrimitive,const drawinglayer::geometry::ViewInformation2D & aViewInformation) const676 void SdrTextObj::impDecomposeContourTextPrimitive(
677 drawinglayer::primitive2d::Primitive2DSequence& rTarget,
678 const drawinglayer::primitive2d::SdrContourTextPrimitive2D& rSdrContourTextPrimitive,
679 const drawinglayer::geometry::ViewInformation2D& aViewInformation) const
680 {
681 // decompose matrix to have position and size of text
682 basegfx::B2DVector aScale, aTranslate;
683 double fRotate, fShearX;
684 rSdrContourTextPrimitive.getObjectTransform().decompose(aScale, aTranslate, fRotate, fShearX);
685
686 // prepare contour polygon, force to non-mirrored for layouting
687 basegfx::B2DPolyPolygon aPolyPolygon(rSdrContourTextPrimitive.getUnitPolyPolygon());
688 aPolyPolygon.transform(basegfx::tools::createScaleB2DHomMatrix(fabs(aScale.getX()), fabs(aScale.getY())));
689
690 // prepare outliner
691 SdrOutliner& rOutliner = ImpGetDrawOutliner();
692 const Size aNullSize;
693 rOutliner.SetPaperSize(aNullSize);
694 rOutliner.SetPolygon(aPolyPolygon);
695 rOutliner.SetUpdateMode(true);
696 rOutliner.SetText(rSdrContourTextPrimitive.getOutlinerParaObject());
697
698 // set visualizing page at Outliner; needed e.g. for PageNumberField decomposition
699 rOutliner.setVisualizedPage(GetSdrPageFromXDrawPage(aViewInformation.getVisualizedPage()));
700
701 // prepare matrices to apply to newly created primitives
702 basegfx::B2DHomMatrix aNewTransformA;
703
704 // mirroring. We are now in the polygon sizes. When mirroring in X and Y,
705 // move the null point which was top left to bottom right.
706 const bool bMirrorX(basegfx::fTools::less(aScale.getX(), 0.0));
707 const bool bMirrorY(basegfx::fTools::less(aScale.getY(), 0.0));
708
709 // in-between the translations of the single primitives will take place. Afterwards,
710 // the object's transformations need to be applied
711 const basegfx::B2DHomMatrix aNewTransformB(basegfx::tools::createScaleShearXRotateTranslateB2DHomMatrix(
712 bMirrorX ? -1.0 : 1.0, bMirrorY ? -1.0 : 1.0,
713 fShearX, fRotate, aTranslate.getX(), aTranslate.getY()));
714
715 // now break up text primitives.
716 impTextBreakupHandler aConverter(rOutliner);
717 aConverter.decomposeContourTextPrimitive(aNewTransformA, aNewTransformB, aScale);
718
719 // cleanup outliner
720 rOutliner.Clear();
721 rOutliner.setVisualizedPage(0);
722
723 rTarget = aConverter.getPrimitive2DSequence();
724 }
725
impDecomposeBlockTextPrimitive(drawinglayer::primitive2d::Primitive2DSequence & rTarget,const drawinglayer::primitive2d::SdrBlockTextPrimitive2D & rSdrBlockTextPrimitive,const drawinglayer::geometry::ViewInformation2D & aViewInformation) const726 void SdrTextObj::impDecomposeBlockTextPrimitive(
727 drawinglayer::primitive2d::Primitive2DSequence& rTarget,
728 const drawinglayer::primitive2d::SdrBlockTextPrimitive2D& rSdrBlockTextPrimitive,
729 const drawinglayer::geometry::ViewInformation2D& aViewInformation) const
730 {
731 // decompose matrix to have position and size of text
732 basegfx::B2DVector aScale, aTranslate;
733 double fRotate, fShearX;
734 rSdrBlockTextPrimitive.getTextRangeTransform().decompose(aScale, aTranslate, fRotate, fShearX);
735
736 // use B2DRange aAnchorTextRange for calculations
737 basegfx::B2DRange aAnchorTextRange(aTranslate);
738 aAnchorTextRange.expand(aTranslate + aScale);
739
740 // prepare outliner
741 const bool bIsCell(rSdrBlockTextPrimitive.getCellText());
742 SdrOutliner& rOutliner = ImpGetDrawOutliner();
743 SdrTextHorzAdjust eHAdj = rSdrBlockTextPrimitive.getSdrTextHorzAdjust();
744 SdrTextVertAdjust eVAdj = rSdrBlockTextPrimitive.getSdrTextVertAdjust();
745 const sal_uInt32 nOriginalControlWord(rOutliner.GetControlWord());
746 const Size aNullSize;
747
748 // set visualizing page at Outliner; needed e.g. for PageNumberField decomposition
749 rOutliner.setVisualizedPage(GetSdrPageFromXDrawPage(aViewInformation.getVisualizedPage()));
750 rOutliner.SetFixedCellHeight(rSdrBlockTextPrimitive.isFixedCellHeight());
751 rOutliner.SetControlWord(nOriginalControlWord|EE_CNTRL_AUTOPAGESIZE);
752 rOutliner.SetMinAutoPaperSize(aNullSize);
753 rOutliner.SetMaxAutoPaperSize(Size(1000000,1000000));
754
755 // add one to rage sizes to get back to the old Rectangle and outliner measurements
756 const sal_uInt32 nAnchorTextWidth(FRound(aAnchorTextRange.getWidth() + 1L));
757 const sal_uInt32 nAnchorTextHeight(FRound(aAnchorTextRange.getHeight() + 1L));
758 const bool bVerticalWritintg(rSdrBlockTextPrimitive.getOutlinerParaObject().IsVertical());
759 const Size aAnchorTextSize(Size(nAnchorTextWidth, nAnchorTextHeight));
760
761 if(bIsCell)
762 {
763 // cell text is formated neither like a text object nor like a object
764 // text, so use a special setup here
765 rOutliner.SetMaxAutoPaperSize(aAnchorTextSize);
766
767 // #i106214# To work with an unchangeable PaperSize (CellSize in
768 // this case) Set(Min|Max)AutoPaperSize and SetPaperSize have to be used.
769 // #i106214# This was not completely correct; to still measure the real
770 // text height to allow vertical adjust (and vice versa for VerticalWritintg)
771 // only one aspect has to be set, but the other one to zero
772 if(bVerticalWritintg)
773 {
774 // measure the horizontal text size
775 rOutliner.SetMinAutoPaperSize(Size(0, aAnchorTextSize.Height()));
776 }
777 else
778 {
779 // measure the vertical text size
780 rOutliner.SetMinAutoPaperSize(Size(aAnchorTextSize.Width(), 0));
781 }
782
783 rOutliner.SetPaperSize(aAnchorTextSize);
784 rOutliner.SetUpdateMode(true);
785 rOutliner.SetText(rSdrBlockTextPrimitive.getOutlinerParaObject());
786 }
787 else
788 {
789 // check if block text is used (only one of them can be true)
790 const bool bHorizontalIsBlock(SDRTEXTHORZADJUST_BLOCK == eHAdj && !bVerticalWritintg);
791 const bool bVerticalIsBlock(SDRTEXTVERTADJUST_BLOCK == eVAdj && bVerticalWritintg);
792
793 // set minimal paper size hor/ver if needed
794 if(bHorizontalIsBlock)
795 {
796 rOutliner.SetMinAutoPaperSize(Size(nAnchorTextWidth, 0));
797 }
798 else if(bVerticalIsBlock)
799 {
800 rOutliner.SetMinAutoPaperSize(Size(0, nAnchorTextHeight));
801 }
802
803 if((rSdrBlockTextPrimitive.getWordWrap() || IsTextFrame()) && !rSdrBlockTextPrimitive.getUnlimitedPage())
804 {
805 // #i103454# maximal paper size hor/ver needs to be limited to text
806 // frame size. If it's block text, still allow the 'other' direction
807 // to grow to get a correct real text size when using GetPaperSize().
808 // When just using aAnchorTextSize as maximum, GetPaperSize()
809 // would just return aAnchorTextSize again: this means, the wanted
810 // 'measurement' of the real size of block text would not work
811 Size aMaxAutoPaperSize(aAnchorTextSize);
812
813 if(bHorizontalIsBlock)
814 {
815 // allow to grow vertical for horizontal blocks
816 aMaxAutoPaperSize.setHeight(1000000);
817 }
818 else if(bVerticalIsBlock)
819 {
820 // allow to grow horizontal for vertical blocks
821 aMaxAutoPaperSize.setWidth(1000000);
822 }
823
824 rOutliner.SetMaxAutoPaperSize(aMaxAutoPaperSize);
825 }
826
827 rOutliner.SetPaperSize(aNullSize);
828 rOutliner.SetUpdateMode(true);
829 rOutliner.SetText(rSdrBlockTextPrimitive.getOutlinerParaObject());
830 }
831
832 rOutliner.SetControlWord(nOriginalControlWord);
833
834 // now get back the layouted text size from outliner
835 const Size aOutlinerTextSiz(rOutliner.GetPaperSize());
836 const basegfx::B2DVector aOutlinerScale(aOutlinerTextSiz.Width(), aOutlinerTextSiz.Height());
837 basegfx::B2DVector aAdjustTranslate(0.0, 0.0);
838
839 // For draw objects containing text correct hor/ver alignment if text is bigger
840 // than the object itself. Without that correction, the text would always be
841 // formatted to the left edge (or top edge when vertical) of the draw object.
842 if(!IsTextFrame() && !bIsCell)
843 {
844 if(aAnchorTextRange.getWidth() < aOutlinerScale.getX() && !bVerticalWritintg)
845 {
846 // Horizontal case here. Correct only if eHAdj == SDRTEXTHORZADJUST_BLOCK,
847 // else the alignment is wanted.
848 if(SDRTEXTHORZADJUST_BLOCK == eHAdj)
849 {
850 eHAdj = SDRTEXTHORZADJUST_CENTER;
851 }
852 }
853
854 if(aAnchorTextRange.getHeight() < aOutlinerScale.getY() && bVerticalWritintg)
855 {
856 // Vertical case here. Correct only if eHAdj == SDRTEXTVERTADJUST_BLOCK,
857 // else the alignment is wanted.
858 if(SDRTEXTVERTADJUST_BLOCK == eVAdj)
859 {
860 eVAdj = SDRTEXTVERTADJUST_CENTER;
861 }
862 }
863 }
864
865 // correct horizontal translation using the now known text size
866 if(SDRTEXTHORZADJUST_CENTER == eHAdj || SDRTEXTHORZADJUST_RIGHT == eHAdj)
867 {
868 const double fFree(aAnchorTextRange.getWidth() - aOutlinerScale.getX());
869
870 if(SDRTEXTHORZADJUST_CENTER == eHAdj)
871 {
872 aAdjustTranslate.setX(fFree / 2.0);
873 }
874
875 if(SDRTEXTHORZADJUST_RIGHT == eHAdj)
876 {
877 aAdjustTranslate.setX(fFree);
878 }
879 }
880
881 // correct vertical translation using the now known text size
882 if(SDRTEXTVERTADJUST_CENTER == eVAdj || SDRTEXTVERTADJUST_BOTTOM == eVAdj)
883 {
884 const double fFree(aAnchorTextRange.getHeight() - aOutlinerScale.getY());
885
886 if(SDRTEXTVERTADJUST_CENTER == eVAdj)
887 {
888 aAdjustTranslate.setY(fFree / 2.0);
889 }
890
891 if(SDRTEXTVERTADJUST_BOTTOM == eVAdj)
892 {
893 aAdjustTranslate.setY(fFree);
894 }
895 }
896
897 // prepare matrices to apply to newly created primitives. aNewTransformA
898 // will get coordinates in aOutlinerScale size and positive in X, Y.
899 // Translate relative to given primitive to get same rotation and shear
900 // as the master shape we are working on. For vertical, use the top-right
901 // corner
902 const double fStartInX(bVerticalWritintg ? aAdjustTranslate.getX() + aOutlinerScale.getX() : aAdjustTranslate.getX());
903 const basegfx::B2DTuple aAdjOffset(fStartInX, aAdjustTranslate.getY());
904 basegfx::B2DHomMatrix aNewTransformA(basegfx::tools::createTranslateB2DHomMatrix(aAdjOffset.getX(), aAdjOffset.getY()));
905
906 // mirroring. We are now in aAnchorTextRange sizes. When mirroring in X and Y,
907 // move the null point which was top left to bottom right.
908 const bool bMirrorX(basegfx::fTools::less(aScale.getX(), 0.0));
909 const bool bMirrorY(basegfx::fTools::less(aScale.getY(), 0.0));
910
911 // in-between the translations of the single primitives will take place. Afterwards,
912 // the object's transformations need to be applied
913 const basegfx::B2DHomMatrix aNewTransformB(basegfx::tools::createScaleShearXRotateTranslateB2DHomMatrix(
914 bMirrorX ? -1.0 : 1.0, bMirrorY ? -1.0 : 1.0,
915 fShearX, fRotate, aTranslate.getX(), aTranslate.getY()));
916
917 // #SJ# create ClipRange (if needed)
918 basegfx::B2DRange aClipRange;
919
920 if(rSdrBlockTextPrimitive.getClipOnBounds())
921 {
922 aClipRange.expand(-aAdjOffset);
923 aClipRange.expand(basegfx::B2DTuple(aAnchorTextSize.Width(), aAnchorTextSize.Height()) - aAdjOffset);
924 }
925
926 // now break up text primitives.
927 impTextBreakupHandler aConverter(rOutliner);
928 aConverter.decomposeBlockTextPrimitive(aNewTransformA, aNewTransformB, aClipRange);
929
930 // cleanup outliner
931 rOutliner.Clear();
932 rOutliner.setVisualizedPage(0);
933
934 rTarget = aConverter.getPrimitive2DSequence();
935 }
936
impDecomposeStretchTextPrimitive(drawinglayer::primitive2d::Primitive2DSequence & rTarget,const drawinglayer::primitive2d::SdrStretchTextPrimitive2D & rSdrStretchTextPrimitive,const drawinglayer::geometry::ViewInformation2D & aViewInformation) const937 void SdrTextObj::impDecomposeStretchTextPrimitive(
938 drawinglayer::primitive2d::Primitive2DSequence& rTarget,
939 const drawinglayer::primitive2d::SdrStretchTextPrimitive2D& rSdrStretchTextPrimitive,
940 const drawinglayer::geometry::ViewInformation2D& aViewInformation) const
941 {
942 // decompose matrix to have position and size of text
943 basegfx::B2DVector aScale, aTranslate;
944 double fRotate, fShearX;
945 rSdrStretchTextPrimitive.getTextRangeTransform().decompose(aScale, aTranslate, fRotate, fShearX);
946
947 // use non-mirrored B2DRange aAnchorTextRange for calculations
948 basegfx::B2DRange aAnchorTextRange(aTranslate);
949 aAnchorTextRange.expand(aTranslate + aScale);
950
951 // prepare outliner
952 SdrOutliner& rOutliner = ImpGetDrawOutliner();
953 const sal_uInt32 nOriginalControlWord(rOutliner.GetControlWord());
954 const Size aNullSize;
955
956 rOutliner.SetControlWord(nOriginalControlWord|EE_CNTRL_STRETCHING|EE_CNTRL_AUTOPAGESIZE);
957 rOutliner.SetFixedCellHeight(rSdrStretchTextPrimitive.isFixedCellHeight());
958 rOutliner.SetMinAutoPaperSize(aNullSize);
959 rOutliner.SetMaxAutoPaperSize(Size(1000000,1000000));
960 rOutliner.SetPaperSize(aNullSize);
961 rOutliner.SetUpdateMode(true);
962 rOutliner.SetText(rSdrStretchTextPrimitive.getOutlinerParaObject());
963
964 // set visualizing page at Outliner; needed e.g. for PageNumberField decomposition
965 rOutliner.setVisualizedPage(GetSdrPageFromXDrawPage(aViewInformation.getVisualizedPage()));
966
967 // now get back the layouted text size from outliner
968 const Size aOutlinerTextSiz(rOutliner.CalcTextSize());
969 const basegfx::B2DVector aOutlinerScale(
970 basegfx::fTools::equalZero(aOutlinerTextSiz.Width()) ? 1.0 : aOutlinerTextSiz.Width(),
971 basegfx::fTools::equalZero(aOutlinerTextSiz.Height()) ? 1.0 : aOutlinerTextSiz.Height());
972
973 // prepare matrices to apply to newly created primitives
974 basegfx::B2DHomMatrix aNewTransformA;
975
976 // #i101957# Check for vertical text. If used, aNewTransformA
977 // needs to translate the text initially around object width to orient
978 // it relative to the topper right instead of the topper left
979 const bool bVertical(rSdrStretchTextPrimitive.getOutlinerParaObject().IsVertical());
980
981 if(bVertical)
982 {
983 aNewTransformA.translate(aScale.getX(), 0.0);
984 }
985
986 // calculate global char stretching scale parameters. Use non-mirrored sizes
987 // to layout without mirroring
988 const double fScaleX(fabs(aScale.getX()) / aOutlinerScale.getX());
989 const double fScaleY(fabs(aScale.getY()) / aOutlinerScale.getY());
990 rOutliner.SetGlobalCharStretching((sal_Int16)FRound(fScaleX * 100.0), (sal_Int16)FRound(fScaleY * 100.0));
991
992 // mirroring. We are now in aAnchorTextRange sizes. When mirroring in X and Y,
993 // move the null point which was top left to bottom right.
994 const bool bMirrorX(basegfx::fTools::less(aScale.getX(), 0.0));
995 const bool bMirrorY(basegfx::fTools::less(aScale.getY(), 0.0));
996
997 // in-between the translations of the single primitives will take place. Afterwards,
998 // the object's transformations need to be applied
999 const basegfx::B2DHomMatrix aNewTransformB(basegfx::tools::createScaleShearXRotateTranslateB2DHomMatrix(
1000 bMirrorX ? -1.0 : 1.0, bMirrorY ? -1.0 : 1.0,
1001 fShearX, fRotate, aTranslate.getX(), aTranslate.getY()));
1002
1003 // now break up text primitives.
1004 impTextBreakupHandler aConverter(rOutliner);
1005 aConverter.decomposeStretchTextPrimitive(aNewTransformA, aNewTransformB);
1006
1007 // cleanup outliner
1008 rOutliner.SetControlWord(nOriginalControlWord);
1009 rOutliner.Clear();
1010 rOutliner.setVisualizedPage(0);
1011
1012 rTarget = aConverter.getPrimitive2DSequence();
1013 }
1014
1015 //////////////////////////////////////////////////////////////////////////////
1016 // timing generators
1017 #define ENDLESS_LOOP (0xffffffff)
1018 #define ENDLESS_TIME ((double)0xffffffff)
1019 #define PIXEL_DPI (96.0)
1020
impGetBlinkTextTiming(drawinglayer::animation::AnimationEntryList & rAnimList) const1021 void SdrTextObj::impGetBlinkTextTiming(drawinglayer::animation::AnimationEntryList& rAnimList) const
1022 {
1023 if(SDRTEXTANI_BLINK == GetTextAniKind())
1024 {
1025 // get values
1026 const SfxItemSet& rSet = GetObjectItemSet();
1027 const sal_uInt32 nRepeat((sal_uInt32)((SdrTextAniCountItem&)rSet.Get(SDRATTR_TEXT_ANICOUNT)).GetValue());
1028 bool bVisisbleWhenStopped(((SdrTextAniStopInsideItem&)rSet.Get(SDRATTR_TEXT_ANISTOPINSIDE)).GetValue());
1029 double fDelay((double)((SdrTextAniDelayItem&)rSet.Get(SDRATTR_TEXT_ANIDELAY)).GetValue());
1030
1031 if(0.0 == fDelay)
1032 {
1033 // use default
1034 fDelay = 250.0;
1035 }
1036
1037 // prepare loop and add
1038 drawinglayer::animation::AnimationEntryLoop aLoop(nRepeat ? nRepeat : ENDLESS_LOOP);
1039 drawinglayer::animation::AnimationEntryFixed aStart(fDelay, 0.0);
1040 aLoop.append(aStart);
1041 drawinglayer::animation::AnimationEntryFixed aEnd(fDelay, 1.0);
1042 aLoop.append(aEnd);
1043 rAnimList.append(aLoop);
1044
1045 // add stopped state if loop is not endless
1046 if(0L != nRepeat)
1047 {
1048 drawinglayer::animation::AnimationEntryFixed aStop(ENDLESS_TIME, bVisisbleWhenStopped ? 0.0 : 1.0);
1049 rAnimList.append(aStop);
1050 }
1051 }
1052 }
1053
impCreateScrollTiming(const SfxItemSet & rSet,drawinglayer::animation::AnimationEntryList & rAnimList,bool bForward,double fTimeFullPath,double fFrequency)1054 void impCreateScrollTiming(const SfxItemSet& rSet, drawinglayer::animation::AnimationEntryList& rAnimList, bool bForward, double fTimeFullPath, double fFrequency)
1055 {
1056 bool bVisisbleWhenStopped(((SdrTextAniStopInsideItem&)rSet.Get(SDRATTR_TEXT_ANISTOPINSIDE)).GetValue());
1057 bool bVisisbleWhenStarted(((SdrTextAniStartInsideItem&)rSet.Get(SDRATTR_TEXT_ANISTOPINSIDE )).GetValue());
1058 const sal_uInt32 nRepeat(((SdrTextAniCountItem&)rSet.Get(SDRATTR_TEXT_ANICOUNT)).GetValue());
1059
1060 if(bVisisbleWhenStarted)
1061 {
1062 // move from center to outside
1063 drawinglayer::animation::AnimationEntryLinear aInOut(fTimeFullPath * 0.5, fFrequency, 0.5, bForward ? 1.0 : 0.0);
1064 rAnimList.append(aInOut);
1065 }
1066
1067 // loop. In loop, move through
1068 if(nRepeat || 0L == nRepeat)
1069 {
1070 drawinglayer::animation::AnimationEntryLoop aLoop(nRepeat ? nRepeat : ENDLESS_LOOP);
1071 drawinglayer::animation::AnimationEntryLinear aThrough(fTimeFullPath, fFrequency, bForward ? 0.0 : 1.0, bForward ? 1.0 : 0.0);
1072 aLoop.append(aThrough);
1073 rAnimList.append(aLoop);
1074 }
1075
1076 if(0L != nRepeat && bVisisbleWhenStopped)
1077 {
1078 // move from outside to center
1079 drawinglayer::animation::AnimationEntryLinear aOutIn(fTimeFullPath * 0.5, fFrequency, bForward ? 0.0 : 1.0, 0.5);
1080 rAnimList.append(aOutIn);
1081
1082 // add timing for staying at the end
1083 drawinglayer::animation::AnimationEntryFixed aEnd(ENDLESS_TIME, 0.5);
1084 rAnimList.append(aEnd);
1085 }
1086 }
1087
impCreateAlternateTiming(const SfxItemSet & rSet,drawinglayer::animation::AnimationEntryList & rAnimList,double fRelativeTextLength,bool bForward,double fTimeFullPath,double fFrequency)1088 void impCreateAlternateTiming(const SfxItemSet& rSet, drawinglayer::animation::AnimationEntryList& rAnimList, double fRelativeTextLength, bool bForward, double fTimeFullPath, double fFrequency)
1089 {
1090 if(basegfx::fTools::more(fRelativeTextLength, 0.5))
1091 {
1092 // this is the case when fTextLength > fFrameLength, text is bigger than animation frame.
1093 // In that case, correct direction
1094 bForward = !bForward;
1095 }
1096
1097 const double fStartPosition(bForward ? fRelativeTextLength : 1.0 - fRelativeTextLength);
1098 const double fEndPosition(bForward ? 1.0 - fRelativeTextLength : fRelativeTextLength);
1099 bool bVisisbleWhenStopped(((SdrTextAniStopInsideItem&)rSet.Get(SDRATTR_TEXT_ANISTOPINSIDE)).GetValue());
1100 bool bVisisbleWhenStarted(((SdrTextAniStartInsideItem&)rSet.Get(SDRATTR_TEXT_ANISTOPINSIDE )).GetValue());
1101 const sal_uInt32 nRepeat(((SdrTextAniCountItem&)rSet.Get(SDRATTR_TEXT_ANICOUNT)).GetValue());
1102
1103 if(!bVisisbleWhenStarted)
1104 {
1105 // move from outside to center
1106 drawinglayer::animation::AnimationEntryLinear aOutIn(fTimeFullPath * 0.5, fFrequency, bForward ? 0.0 : 1.0, 0.5);
1107 rAnimList.append(aOutIn);
1108 }
1109
1110 // loop. In loop, move out and in again. fInnerMovePath may be negative when text is bigger then frame,
1111 // so use absolute value
1112 const double fInnerMovePath(fabs(1.0 - (fRelativeTextLength * 2.0)));
1113 const double fTimeForInnerPath(fTimeFullPath * fInnerMovePath);
1114 const double fHalfInnerPath(fTimeForInnerPath * 0.5);
1115 const sal_uInt32 nDoubleRepeat(nRepeat / 2L);
1116
1117 if(nDoubleRepeat || 0L == nRepeat)
1118 {
1119 // double forth and back loop
1120 drawinglayer::animation::AnimationEntryLoop aLoop(nDoubleRepeat ? nDoubleRepeat : ENDLESS_LOOP);
1121 drawinglayer::animation::AnimationEntryLinear aTime0(fHalfInnerPath, fFrequency, 0.5, fEndPosition);
1122 aLoop.append(aTime0);
1123 drawinglayer::animation::AnimationEntryLinear aTime1(fTimeForInnerPath, fFrequency, fEndPosition, fStartPosition);
1124 aLoop.append(aTime1);
1125 drawinglayer::animation::AnimationEntryLinear aTime2(fHalfInnerPath, fFrequency, fStartPosition, 0.5);
1126 aLoop.append(aTime2);
1127 rAnimList.append(aLoop);
1128 }
1129
1130 if(nRepeat % 2L)
1131 {
1132 // repeat is uneven, so we need one more forth and back to center
1133 drawinglayer::animation::AnimationEntryLinear aTime0(fHalfInnerPath, fFrequency, 0.5, fEndPosition);
1134 rAnimList.append(aTime0);
1135 drawinglayer::animation::AnimationEntryLinear aTime1(fHalfInnerPath, fFrequency, fEndPosition, 0.5);
1136 rAnimList.append(aTime1);
1137 }
1138
1139 if(0L != nRepeat)
1140 {
1141 if(bVisisbleWhenStopped)
1142 {
1143 // add timing for staying at the end
1144 drawinglayer::animation::AnimationEntryFixed aEnd(ENDLESS_TIME, 0.5);
1145 rAnimList.append(aEnd);
1146 }
1147 else
1148 {
1149 // move from center to outside
1150 drawinglayer::animation::AnimationEntryLinear aInOut(fTimeFullPath * 0.5, fFrequency, 0.5, bForward ? 1.0 : 0.0);
1151 rAnimList.append(aInOut);
1152 }
1153 }
1154 }
1155
impCreateSlideTiming(const SfxItemSet & rSet,drawinglayer::animation::AnimationEntryList & rAnimList,bool bForward,double fTimeFullPath,double fFrequency)1156 void impCreateSlideTiming(const SfxItemSet& rSet, drawinglayer::animation::AnimationEntryList& rAnimList, bool bForward, double fTimeFullPath, double fFrequency)
1157 {
1158 // move in from outside, start outside
1159 const double fStartPosition(bForward ? 0.0 : 1.0);
1160 const sal_uInt32 nRepeat(((SdrTextAniCountItem&)rSet.Get(SDRATTR_TEXT_ANICOUNT)).GetValue());
1161
1162 // move from outside to center
1163 drawinglayer::animation::AnimationEntryLinear aOutIn(fTimeFullPath * 0.5, fFrequency, fStartPosition, 0.5);
1164 rAnimList.append(aOutIn);
1165
1166 // loop. In loop, move out and in again
1167 if(nRepeat > 1L || 0L == nRepeat)
1168 {
1169 drawinglayer::animation::AnimationEntryLoop aLoop(nRepeat ? nRepeat - 1L : ENDLESS_LOOP);
1170 drawinglayer::animation::AnimationEntryLinear aTime0(fTimeFullPath * 0.5, fFrequency, 0.5, fStartPosition);
1171 aLoop.append(aTime0);
1172 drawinglayer::animation::AnimationEntryLinear aTime1(fTimeFullPath * 0.5, fFrequency, fStartPosition, 0.5);
1173 aLoop.append(aTime1);
1174 rAnimList.append(aLoop);
1175 }
1176
1177 // always visible when stopped, so add timing for staying at the end when not endless
1178 if(0L != nRepeat)
1179 {
1180 drawinglayer::animation::AnimationEntryFixed aEnd(ENDLESS_TIME, 0.5);
1181 rAnimList.append(aEnd);
1182 }
1183 }
1184
impGetScrollTextTiming(drawinglayer::animation::AnimationEntryList & rAnimList,double fFrameLength,double fTextLength) const1185 void SdrTextObj::impGetScrollTextTiming(drawinglayer::animation::AnimationEntryList& rAnimList, double fFrameLength, double fTextLength) const
1186 {
1187 const SdrTextAniKind eAniKind(GetTextAniKind());
1188
1189 if(SDRTEXTANI_SCROLL == eAniKind || SDRTEXTANI_ALTERNATE == eAniKind || SDRTEXTANI_SLIDE == eAniKind)
1190 {
1191 // get data. Goal is to calculate fTimeFullPath which is the time needed to
1192 // move animation from (0.0) to (1.0) state
1193 const SfxItemSet& rSet = GetObjectItemSet();
1194 double fAnimationDelay((double)((SdrTextAniDelayItem&)rSet.Get(SDRATTR_TEXT_ANIDELAY)).GetValue());
1195 double fSingleStepWidth((double)((SdrTextAniAmountItem&)rSet.Get(SDRATTR_TEXT_ANIAMOUNT)).GetValue());
1196 const SdrTextAniDirection eDirection(GetTextAniDirection());
1197 const bool bForward(SDRTEXTANI_RIGHT == eDirection || SDRTEXTANI_DOWN == eDirection);
1198
1199 if(basegfx::fTools::equalZero(fAnimationDelay))
1200 {
1201 // default to 1/20 second
1202 fAnimationDelay = 50.0;
1203 }
1204
1205 if(basegfx::fTools::less(fSingleStepWidth, 0.0))
1206 {
1207 // data is in pixels, convert to logic. Imply PIXEL_DPI dpi.
1208 // It makes no sense to keep the view-transformation centered
1209 // definitions, so get rid of them here.
1210 fSingleStepWidth = (-fSingleStepWidth * (2540.0 / PIXEL_DPI));
1211 }
1212
1213 if(basegfx::fTools::equalZero(fSingleStepWidth))
1214 {
1215 // default to 1 milimeter
1216 fSingleStepWidth = 100.0;
1217 }
1218
1219 // use the length of the full animation path and the number of steps
1220 // to get the full path time
1221 const double fFullPathLength(fFrameLength + fTextLength);
1222 const double fNumberOfSteps(fFullPathLength / fSingleStepWidth);
1223 double fTimeFullPath(fNumberOfSteps * fAnimationDelay);
1224
1225 if(fTimeFullPath < fAnimationDelay)
1226 {
1227 fTimeFullPath = fAnimationDelay;
1228 }
1229
1230 switch(eAniKind)
1231 {
1232 case SDRTEXTANI_SCROLL :
1233 {
1234 impCreateScrollTiming(rSet, rAnimList, bForward, fTimeFullPath, fAnimationDelay);
1235 break;
1236 }
1237 case SDRTEXTANI_ALTERNATE :
1238 {
1239 double fRelativeTextLength(fTextLength / (fFrameLength + fTextLength));
1240 impCreateAlternateTiming(rSet, rAnimList, fRelativeTextLength, bForward, fTimeFullPath, fAnimationDelay);
1241 break;
1242 }
1243 case SDRTEXTANI_SLIDE :
1244 {
1245 impCreateSlideTiming(rSet, rAnimList, bForward, fTimeFullPath, fAnimationDelay);
1246 break;
1247 }
1248 default : break; // SDRTEXTANI_NONE, SDRTEXTANI_BLINK
1249 }
1250 }
1251 }
1252
1253 //////////////////////////////////////////////////////////////////////////////
1254 // eof
1255