Coverage Report

Created: 2026-04-09 11:41

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/libreoffice/svx/source/svdraw/svdotextpathdecomposition.cxx
Line
Count
Source
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
#include <sal/config.h>
21
22
#include <o3tl/safeint.hxx>
23
#include <svx/svdotext.hxx>
24
#include <svx/svdoutl.hxx>
25
#include <basegfx/vector/b2dvector.hxx>
26
#include <sdr/primitive2d/sdrtextprimitive2d.hxx>
27
#include <basegfx/polygon/b2dpolygontools.hxx>
28
#include <basegfx/polygon/b2dpolygon.hxx>
29
#include <algorithm>
30
#include <com/sun/star/i18n/BreakIterator.hpp>
31
#include <comphelper/processfactory.hxx>
32
#include <com/sun/star/i18n/CharacterIteratorMode.hpp>
33
#include <drawinglayer/primitive2d/textlayoutdevice.hxx>
34
#include <drawinglayer/primitive2d/textprimitive2d.hxx>
35
#include <basegfx/color/bcolor.hxx>
36
#include <editeng/StripPortionsHelper.hxx>
37
38
// primitive decomposition helpers
39
#include <drawinglayer/attribute/strokeattribute.hxx>
40
#include <drawinglayer/primitive2d/PolygonStrokePrimitive2D.hxx>
41
#include <drawinglayer/primitive2d/unifiedtransparenceprimitive2d.hxx>
42
#include <svx/unoapi.hxx>
43
#include <drawinglayer/geometry/viewinformation2d.hxx>
44
#include <sdr/attribute/sdrformtextoutlineattribute.hxx>
45
#include <utility>
46
47
using namespace com::sun::star;
48
49
// PathTextPortion helper
50
51
namespace
52
{
53
    class impPathTextPortion
54
    {
55
        basegfx::B2DVector                          maOffset;
56
        OUString                                    maText;
57
        sal_Int32                                   mnTextStart;
58
        sal_Int32                                   mnTextLength;
59
        sal_Int32                                   mnParagraph;
60
        SvxFont                                     maFont;
61
        ::std::vector< double >                     maDblDXArray;   // double DXArray, font size independent -> unit coordinate system
62
        ::std::vector< sal_Bool >                   maKashidaArray;
63
        lang::Locale                           maLocale;
64
65
        bool                                        mbRTL : 1;
66
67
    public:
68
        explicit impPathTextPortion(const DrawPortionInfo& rInfo)
69
0
        :   maOffset(rInfo.mrStartPos.X(), rInfo.mrStartPos.Y()),
70
0
            maText(rInfo.maText),
71
0
            mnTextStart(rInfo.mnTextStart),
72
0
            mnTextLength(rInfo.mnTextLen),
73
0
            mnParagraph(rInfo.mnPara),
74
0
            maFont(rInfo.mrFont),
75
0
            maKashidaArray(rInfo.mpKashidaArray.begin(), rInfo.mpKashidaArray.end()),
76
0
            maLocale(rInfo.mpLocale ? *rInfo.mpLocale : lang::Locale()),
77
0
            mbRTL(!rInfo.mrFont.IsVertical() && rInfo.IsRTL())
78
0
        {
79
0
            if(mnTextLength && !rInfo.mpDXArray.empty())
80
0
            {
81
0
                maDblDXArray.reserve(mnTextLength);
82
83
0
                for(sal_Int32 a=0; a < mnTextLength; a++)
84
0
                {
85
0
                    maDblDXArray.push_back(rInfo.mpDXArray[a]);
86
0
                }
87
0
            }
88
0
        }
89
90
        // for ::std::sort
91
        bool operator<(const impPathTextPortion& rComp) const
92
0
        {
93
0
            if(mnParagraph < rComp.mnParagraph)
94
0
            {
95
0
                return true;
96
0
            }
97
98
0
            if(maOffset.getX() < rComp.maOffset.getX())
99
0
            {
100
0
                return true;
101
0
            }
102
103
0
            return (maOffset.getY() < rComp.maOffset.getY());
104
0
        }
105
106
0
        const OUString& getText() const { return maText; }
107
0
        sal_Int32 getTextStart() const { return mnTextStart; }
108
0
        sal_Int32 getTextLength() const { return mnTextLength; }
109
0
        sal_Int32 getParagraph() const { return mnParagraph; }
110
0
        const SvxFont& getFont() const { return maFont; }
111
0
        bool isRTL() const { return mbRTL; }
112
0
        const ::std::vector< double >& getDoubleDXArray() const { return maDblDXArray; }
113
0
        const ::std::vector< sal_Bool >& getKashidaArray() const { return maKashidaArray; }
114
0
        const lang::Locale& getLocale() const { return maLocale; }
115
116
        sal_Int32 getPortionIndex(sal_Int32 nIndex, sal_Int32 nLength) const
117
0
        {
118
0
            if(mbRTL)
119
0
            {
120
0
                return (mnTextStart + (mnTextLength - (nIndex + nLength)));
121
0
            }
122
0
            else
123
0
            {
124
0
                return (mnTextStart + nIndex);
125
0
            }
126
0
        }
127
128
        double getDisplayLength(sal_Int32 nIndex, sal_Int32 nLength) const
129
0
        {
130
0
            drawinglayer::primitive2d::TextLayouterDevice aTextLayouter;
131
0
            double fRetval(0.0);
132
133
0
            if(maFont.IsVertical())
134
0
            {
135
0
                fRetval = aTextLayouter.getTextHeight() * static_cast<double>(nLength);
136
0
            }
137
0
            else
138
0
            {
139
0
                fRetval = aTextLayouter.getTextWidth(maText, getPortionIndex(nIndex, nLength), nLength);
140
0
            }
141
142
0
            return fRetval;
143
0
        }
144
    };
145
} // end of anonymous namespace
146
147
148
// TextBreakup helper
149
150
namespace
151
{
152
    class TextHierarchyBreakupPathTextPortions : public StripPortionsHelper
153
    {
154
        ::std::vector< impPathTextPortion >         maPathTextPortions;
155
156
    public:
157
        virtual void processDrawPortionInfo(const DrawPortionInfo& rDrawPortionInfo) override
158
0
        {
159
            // extract and add data for TextOnPath further processing
160
0
            maPathTextPortions.emplace_back(rDrawPortionInfo);
161
0
        }
162
163
        virtual void processDrawBulletInfo(const DrawBulletInfo&) override
164
0
        {
165
            // nothing to do here, bullets are for now ignored for TextOnLine
166
0
        }
167
168
        virtual void directlyAddB2DPrimitive(const drawinglayer::primitive2d::Primitive2DReference&) override
169
0
        {
170
            // nothing to do here, no support for directly adding Primitives
171
0
        }
172
173
        const ::std::vector< impPathTextPortion >& sortAndGetPathTextPortions()
174
0
        {
175
0
            if(!maPathTextPortions.empty())
176
0
            {
177
                // sort portions by paragraph, x and y
178
0
                ::std::sort(maPathTextPortions.begin(), maPathTextPortions.end());
179
0
            }
180
181
0
            return maPathTextPortions;
182
0
        }
183
    };
184
} // end of anonymous namespace
185
186
187
// TextBreakup one poly and one paragraph helper
188
189
namespace
190
{
191
    class impPolygonParagraphHandler
192
    {
193
        const drawinglayer::attribute::SdrFormTextAttribute         maSdrFormTextAttribute; // FormText parameters
194
        drawinglayer::primitive2d::Primitive2DContainer&            mrDecomposition;        // destination primitive list
195
        drawinglayer::primitive2d::Primitive2DContainer&            mrShadowDecomposition;  // destination primitive list for shadow
196
        uno::Reference<i18n::XBreakIterator>                     mxBreak;                // break iterator
197
198
        static double getParagraphTextLength(const ::std::vector< const impPathTextPortion* >& rTextPortions)
199
0
        {
200
0
            drawinglayer::primitive2d::TextLayouterDevice aTextLayouter;
201
0
            double fRetval(0.0);
202
203
0
            for(const impPathTextPortion* pCandidate : rTextPortions)
204
0
            {
205
0
                if(pCandidate && pCandidate->getTextLength())
206
0
                {
207
0
                    aTextLayouter.setFont(pCandidate->getFont());
208
0
                    fRetval += pCandidate->getDisplayLength(0, pCandidate->getTextLength());
209
0
                }
210
0
            }
211
212
0
            return fRetval;
213
0
        }
214
215
        sal_Int32 getNextGlyphLen(const impPathTextPortion* pCandidate, sal_Int32 nPosition, const lang::Locale& rFontLocale)
216
0
        {
217
0
            sal_Int32 nNextGlyphLen(1);
218
219
0
            if(mxBreak.is())
220
0
            {
221
0
                sal_Int32 nDone(0);
222
0
                nNextGlyphLen = mxBreak->nextCharacters(pCandidate->getText(), nPosition,
223
0
                    rFontLocale, i18n::CharacterIteratorMode::SKIPCELL, 1, nDone) - nPosition;
224
0
            }
225
226
0
            return nNextGlyphLen;
227
0
        }
228
229
    public:
230
        impPolygonParagraphHandler(
231
            drawinglayer::attribute::SdrFormTextAttribute aSdrFormTextAttribute,
232
            drawinglayer::primitive2d::Primitive2DContainer& rDecomposition,
233
            drawinglayer::primitive2d::Primitive2DContainer& rShadowDecomposition)
234
0
        :   maSdrFormTextAttribute(std::move(aSdrFormTextAttribute)),
235
0
            mrDecomposition(rDecomposition),
236
0
            mrShadowDecomposition(rShadowDecomposition)
237
0
        {
238
            // prepare BreakIterator
239
0
            const uno::Reference<uno::XComponentContext>& xContext = ::comphelper::getProcessComponentContext();
240
0
            mxBreak = i18n::BreakIterator::create(xContext);
241
0
        }
242
243
        void HandlePair(const basegfx::B2DPolygon& rPolygonCandidate, const ::std::vector< const impPathTextPortion* >& rTextPortions)
244
0
        {
245
            // prepare polygon geometry, take into account as many parameters as possible
246
0
            basegfx::B2DPolygon aPolygonCandidate(rPolygonCandidate);
247
0
            const double fPolyLength(basegfx::utils::getLength(aPolygonCandidate));
248
0
            double fPolyEnd(fPolyLength);
249
0
            double fPolyStart(0.0);
250
0
            double fAutosizeScaleFactor(1.0);
251
0
            bool bAutosizeScale(false);
252
253
0
            if(maSdrFormTextAttribute.getFormTextMirror())
254
0
            {
255
0
                aPolygonCandidate.flip();
256
0
            }
257
258
0
            if(maSdrFormTextAttribute.getFormTextStart()
259
0
                && (XFormTextAdjust::Left == maSdrFormTextAttribute.getFormTextAdjust()
260
0
                    || XFormTextAdjust::Right == maSdrFormTextAttribute.getFormTextAdjust()))
261
0
            {
262
0
                if(XFormTextAdjust::Left == maSdrFormTextAttribute.getFormTextAdjust())
263
0
                {
264
0
                    fPolyStart += maSdrFormTextAttribute.getFormTextStart();
265
266
0
                    if(fPolyStart > fPolyEnd)
267
0
                    {
268
0
                        fPolyStart = fPolyEnd;
269
0
                    }
270
0
                }
271
0
                else
272
0
                {
273
0
                    fPolyEnd -= maSdrFormTextAttribute.getFormTextStart();
274
275
0
                    if(fPolyEnd < fPolyStart)
276
0
                    {
277
0
                        fPolyEnd = fPolyStart;
278
0
                    }
279
0
                }
280
0
            }
281
282
0
            if(XFormTextAdjust::Left != maSdrFormTextAttribute.getFormTextAdjust())
283
0
            {
284
                // calculate total text length of this paragraph, some layout needs to be done
285
0
                const double fParagraphTextLength(getParagraphTextLength(rTextPortions));
286
287
                // check if text is too long for paragraph. If yes, handle as if left aligned (default),
288
                // but still take care of XFormTextAdjust::AutoSize in that case
289
0
                const bool bTextTooLong(fParagraphTextLength > (fPolyEnd - fPolyStart));
290
291
0
                if(XFormTextAdjust::Right == maSdrFormTextAttribute.getFormTextAdjust())
292
0
                {
293
0
                    if(!bTextTooLong)
294
0
                    {
295
                        // if right aligned, add difference to polygon start
296
0
                        fPolyStart += ((fPolyEnd - fPolyStart) - fParagraphTextLength);
297
0
                    }
298
0
                }
299
0
                else if(XFormTextAdjust::Center == maSdrFormTextAttribute.getFormTextAdjust())
300
0
                {
301
0
                    if(!bTextTooLong)
302
0
                    {
303
                        // if centered, add half of difference to polygon start
304
0
                        fPolyStart += ((fPolyEnd - fPolyStart) - fParagraphTextLength) / 2.0;
305
0
                    }
306
0
                }
307
0
                else if(XFormTextAdjust::AutoSize == maSdrFormTextAttribute.getFormTextAdjust())
308
0
                {
309
                    // if scale, prepare scale factor between curve length and text length
310
0
                    if(0.0 != fParagraphTextLength)
311
0
                    {
312
0
                        fAutosizeScaleFactor = (fPolyEnd - fPolyStart) / fParagraphTextLength;
313
0
                        bAutosizeScale = true;
314
0
                    }
315
0
                }
316
0
            }
317
318
            // handle text portions for this paragraph
319
0
            for(auto a = rTextPortions.begin(); a != rTextPortions.end() && fPolyStart < fPolyEnd; ++a)
320
0
            {
321
0
                const impPathTextPortion* pCandidate = *a;
322
0
                basegfx::B2DVector aFontScaling;
323
324
0
                if(pCandidate && pCandidate->getTextLength())
325
0
                {
326
0
                    const drawinglayer::attribute::FontAttribute aCandidateFontAttribute(
327
0
                        drawinglayer::primitive2d::getFontAttributeFromVclFont(
328
0
                            aFontScaling,
329
0
                            pCandidate->getFont(),
330
0
                            pCandidate->isRTL(),
331
0
                            false));
332
333
0
                    drawinglayer::primitive2d::TextLayouterDevice aTextLayouter;
334
0
                    aTextLayouter.setFont(pCandidate->getFont());
335
0
                    sal_Int32 nUsedTextLength(0);
336
337
0
                    while(nUsedTextLength < pCandidate->getTextLength() && fPolyStart < fPolyEnd)
338
0
                    {
339
0
                        sal_Int32 nNextGlyphLen(getNextGlyphLen(pCandidate, pCandidate->getTextStart() + nUsedTextLength, pCandidate->getLocale()));
340
341
                        // prepare portion length. Takes RTL sections into account.
342
0
                        double fPortionLength(pCandidate->getDisplayLength(nUsedTextLength, nNextGlyphLen));
343
344
0
                        if(bAutosizeScale)
345
0
                        {
346
                            // when autosize scaling, expand portion length
347
0
                            fPortionLength *= fAutosizeScaleFactor;
348
0
                        }
349
350
                        // create transformation
351
0
                        basegfx::B2DHomMatrix aNewTransformA, aNewTransformB, aNewShadowTransform;
352
0
                        basegfx::B2DPoint aStartPos(basegfx::utils::getPositionAbsolute(aPolygonCandidate, fPolyStart, fPolyLength));
353
0
                        basegfx::B2DPoint aEndPos(aStartPos);
354
355
                        // add font scaling
356
0
                        aNewTransformA.scale(aFontScaling.getX(), aFontScaling.getY());
357
358
                        // prepare scaling of text primitive
359
0
                        if(bAutosizeScale)
360
0
                        {
361
                            // when autosize scaling, expand text primitive scaling to it
362
0
                            aNewTransformA.scale(fAutosizeScaleFactor, fAutosizeScaleFactor);
363
0
                        }
364
365
                        // eventually create shadow primitives from aDecomposition and add to rDecomposition
366
0
                        const bool bShadow(XFormTextShadow::NONE != maSdrFormTextAttribute.getFormTextShadow());
367
368
0
                        if(bShadow)
369
0
                        {
370
0
                            if(XFormTextShadow::Normal == maSdrFormTextAttribute.getFormTextShadow())
371
0
                            {
372
0
                                aNewShadowTransform.translate(
373
0
                                    maSdrFormTextAttribute.getFormTextShdwXVal(),
374
0
                                    -maSdrFormTextAttribute.getFormTextShdwYVal());
375
0
                            }
376
0
                            else // XFormTextShadow::Slant
377
0
                            {
378
0
                                double fScaleValue(maSdrFormTextAttribute.getFormTextShdwYVal() / 100.0);
379
0
                                double fShearValue(-basegfx::deg2rad<10>(maSdrFormTextAttribute.getFormTextShdwXVal()));
380
381
0
                                aNewShadowTransform.scale(1.0, fScaleValue);
382
0
                                aNewShadowTransform.shearX(sin(fShearValue));
383
0
                                aNewShadowTransform.scale(1.0, cos(fShearValue));
384
0
                            }
385
0
                        }
386
387
0
                        switch(maSdrFormTextAttribute.getFormTextStyle())
388
0
                        {
389
0
                            case XFormTextStyle::Rotate :
390
0
                            {
391
0
                                aEndPos = basegfx::utils::getPositionAbsolute(aPolygonCandidate, fPolyStart + fPortionLength, fPolyLength);
392
0
                                const basegfx::B2DVector aDirection(aEndPos - aStartPos);
393
0
                                aNewTransformB.rotate(atan2(aDirection.getY(), aDirection.getX()));
394
0
                                aNewTransformB.translate(aStartPos.getX(), aStartPos.getY());
395
396
0
                                break;
397
0
                            }
398
0
                            case XFormTextStyle::Upright :
399
0
                            {
400
0
                                aNewTransformB.translate(aStartPos.getX() - (fPortionLength / 2.0), aStartPos.getY());
401
402
0
                                break;
403
0
                            }
404
0
                            case XFormTextStyle::SlantX :
405
0
                            {
406
0
                                aEndPos = basegfx::utils::getPositionAbsolute(aPolygonCandidate, fPolyStart + fPortionLength, fPolyLength);
407
0
                                const basegfx::B2DVector aDirection(aEndPos - aStartPos);
408
0
                                const double fShearValue(atan2(aDirection.getY(), aDirection.getX()));
409
0
                                const double fSin(sin(fShearValue));
410
0
                                const double fCos(cos(fShearValue));
411
412
0
                                aNewTransformB.shearX(-fSin);
413
414
                                // Scale may lead to objects without height since fCos == 0.0 is possible.
415
                                // Renderers need to handle that, it's not a forbidden value and does not
416
                                // need to be avoided
417
0
                                aNewTransformB.scale(1.0, fCos);
418
0
                                aNewTransformB.translate(aStartPos.getX() - (fPortionLength / 2.0), aStartPos.getY());
419
420
0
                                break;
421
0
                            }
422
0
                            case XFormTextStyle::SlantY :
423
0
                            {
424
0
                                aEndPos = basegfx::utils::getPositionAbsolute(aPolygonCandidate, fPolyStart + fPortionLength, fPolyLength);
425
0
                                const basegfx::B2DVector aDirection(aEndPos - aStartPos);
426
0
                                const double fShearValue(atan2(aDirection.getY(), aDirection.getX()));
427
0
                                const double fCos(cos(fShearValue));
428
0
                                const double fTan(tan(fShearValue));
429
430
                                // shear to 'stand' on the curve
431
0
                                aNewTransformB.shearY(fTan);
432
433
                                // scale in X to make as tight as needed. As with XFT_SLANT_X, this may
434
                                // lead to primitives without width which the renderers will handle
435
0
                                aNewTransformA.scale(fCos, 1.0);
436
437
0
                                aNewTransformB.translate(aStartPos.getX(), aStartPos.getY());
438
439
0
                                break;
440
0
                            }
441
0
                            default : break; // XFormTextStyle::NONE
442
0
                        }
443
444
                        // distance from path?
445
0
                        if(maSdrFormTextAttribute.getFormTextDistance())
446
0
                        {
447
0
                            if(aEndPos.equal(aStartPos))
448
0
                            {
449
0
                                aEndPos = basegfx::utils::getPositionAbsolute(aPolygonCandidate, fPolyStart + fPortionLength, fPolyLength);
450
0
                            }
451
452
                            // use back vector (aStartPos - aEndPos) here to get mirrored perpendicular as in old stuff
453
0
                            const basegfx::B2DVector aPerpendicular(
454
0
                                basegfx::getNormalizedPerpendicular(aStartPos - aEndPos) *
455
0
                                maSdrFormTextAttribute.getFormTextDistance());
456
0
                            aNewTransformB.translate(aPerpendicular.getX(), aPerpendicular.getY());
457
0
                        }
458
459
0
                        if(!pCandidate->getText().isEmpty() && nNextGlyphLen)
460
0
                        {
461
0
                            const sal_Int32 nPortionIndex(pCandidate->getPortionIndex(nUsedTextLength, nNextGlyphLen));
462
0
                            ::std::vector< double > aNewDXArray;
463
464
0
                            if(nNextGlyphLen > 1 && !pCandidate->getDoubleDXArray().empty())
465
0
                            {
466
                                // copy DXArray for portion
467
0
                                aNewDXArray.insert(
468
0
                                    aNewDXArray.begin(),
469
0
                                    pCandidate->getDoubleDXArray().begin() + nPortionIndex,
470
0
                                    pCandidate->getDoubleDXArray().begin() + (nPortionIndex + nNextGlyphLen));
471
472
0
                                if(nPortionIndex > 0)
473
0
                                {
474
                                    // adapt to portion start
475
0
                                    double fDXOffset= *(pCandidate->getDoubleDXArray().begin() + (nPortionIndex - 1));
476
0
                                    ::std::transform(
477
0
                                        aNewDXArray.begin(), aNewDXArray.end(),
478
0
                                        aNewDXArray.begin(), [fDXOffset](double x) { return x - fDXOffset; });
479
0
                                }
480
481
0
                                if(bAutosizeScale)
482
0
                                {
483
                                    // when autosize scaling, adapt to DXArray, too
484
0
                                    ::std::transform(
485
0
                                        aNewDXArray.begin(), aNewDXArray.end(),
486
0
                                        aNewDXArray.begin(), [fAutosizeScaleFactor](double x) { return x * fAutosizeScaleFactor; });
487
0
                                }
488
0
                            }
489
490
0
                            if(bShadow)
491
0
                            {
492
                                // shadow primitive creation
493
0
                                const Color aShadowColor(maSdrFormTextAttribute.getFormTextShdwColor());
494
0
                                const basegfx::BColor aRGBShadowColor(aShadowColor.getBColor());
495
496
0
                                mrShadowDecomposition.push_back(
497
0
                                    new drawinglayer::primitive2d::TextSimplePortionPrimitive2D(
498
0
                                        aNewTransformB * aNewShadowTransform * aNewTransformA,
499
0
                                        pCandidate->getText(),
500
0
                                        nPortionIndex,
501
0
                                        nNextGlyphLen,
502
0
                                        std::vector(aNewDXArray),
503
0
                                        std::vector(pCandidate->getKashidaArray()),
504
0
                                        aCandidateFontAttribute,
505
0
                                        pCandidate->getLocale(),
506
0
                                        aRGBShadowColor) );
507
0
                            }
508
509
0
                            {
510
                                // primitive creation
511
0
                                const Color aColor(pCandidate->getFont().GetColor());
512
0
                                const basegfx::BColor aRGBColor(aColor.getBColor());
513
514
0
                                mrDecomposition.push_back(
515
0
                                    new drawinglayer::primitive2d::TextSimplePortionPrimitive2D(
516
0
                                        aNewTransformB * aNewTransformA,
517
0
                                        pCandidate->getText(),
518
0
                                        nPortionIndex,
519
0
                                        nNextGlyphLen,
520
0
                                        std::move(aNewDXArray),
521
0
                                        std::vector(pCandidate->getKashidaArray()),
522
0
                                        aCandidateFontAttribute,
523
0
                                        pCandidate->getLocale(),
524
0
                                        aRGBColor) );
525
0
                            }
526
0
                        }
527
528
                        // consume from portion
529
0
                        nUsedTextLength += nNextGlyphLen;
530
531
                        // consume from polygon
532
0
                        fPolyStart += fPortionLength;
533
0
                    }
534
0
                }
535
0
            }
536
0
        }
537
    };
538
} // end of anonymous namespace
539
540
541
// primitive decomposition helpers
542
543
namespace
544
{
545
    void impAddPolygonStrokePrimitives(
546
        const basegfx::B2DPolyPolygonVector& rB2DPolyPolyVector,
547
        const basegfx::B2DHomMatrix& rTransform,
548
        const drawinglayer::attribute::LineAttribute& rLineAttribute,
549
        const drawinglayer::attribute::StrokeAttribute& rStrokeAttribute,
550
        drawinglayer::primitive2d::Primitive2DContainer& rTarget)
551
0
    {
552
0
        for(const auto& rB2DPolyPolygon : rB2DPolyPolyVector)
553
0
        {
554
            // prepare PolyPolygons
555
0
            basegfx::B2DPolyPolygon aB2DPolyPolygon = rB2DPolyPolygon;
556
0
            aB2DPolyPolygon.transform(rTransform);
557
558
0
            for(auto const& rPolygon : std::as_const(aB2DPolyPolygon))
559
0
            {
560
                // create one primitive per polygon
561
0
                rTarget.push_back(
562
0
                    new drawinglayer::primitive2d::PolygonStrokePrimitive2D(
563
0
                        rPolygon, rLineAttribute, rStrokeAttribute) );
564
0
            }
565
0
        }
566
0
    }
567
568
    drawinglayer::primitive2d::Primitive2DContainer impAddPathTextOutlines(
569
        const drawinglayer::primitive2d::Primitive2DContainer& rSource,
570
        const drawinglayer::attribute::SdrFormTextOutlineAttribute& rOutlineAttribute)
571
0
    {
572
0
        drawinglayer::primitive2d::Primitive2DContainer aNewPrimitives;
573
574
0
        for(const drawinglayer::primitive2d::Primitive2DReference& a : rSource)
575
0
        {
576
0
            const drawinglayer::primitive2d::TextSimplePortionPrimitive2D* pTextCandidate = dynamic_cast< const drawinglayer::primitive2d::TextSimplePortionPrimitive2D* >(a.get());
577
578
0
            if(pTextCandidate)
579
0
            {
580
0
                basegfx::B2DPolyPolygonVector aB2DPolyPolyVector;
581
0
                basegfx::B2DHomMatrix aPolygonTransform;
582
583
                // get text outlines and their object transformation
584
0
                pTextCandidate->getTextOutlinesAndTransformation(aB2DPolyPolyVector, aPolygonTransform);
585
586
0
                if(!aB2DPolyPolyVector.empty())
587
0
                {
588
                    // create stroke primitives
589
0
                    drawinglayer::primitive2d::Primitive2DContainer aStrokePrimitives;
590
0
                    impAddPolygonStrokePrimitives(
591
0
                        aB2DPolyPolyVector,
592
0
                        aPolygonTransform,
593
0
                        rOutlineAttribute.getLineAttribute(),
594
0
                        rOutlineAttribute.getStrokeAttribute(),
595
0
                        aStrokePrimitives);
596
597
0
                    if(!aStrokePrimitives.empty())
598
0
                    {
599
0
                        if(rOutlineAttribute.getTransparence())
600
0
                        {
601
                            // create UnifiedTransparencePrimitive2D
602
0
                            aNewPrimitives.push_back(
603
0
                                new drawinglayer::primitive2d::UnifiedTransparencePrimitive2D(
604
0
                                    std::move(aStrokePrimitives),
605
0
                                    static_cast<double>(rOutlineAttribute.getTransparence()) / 100.0) );
606
0
                        }
607
0
                        else
608
0
                        {
609
                            // add polygons to rDecomposition as polygonStrokePrimitives
610
0
                            aNewPrimitives.append( std::move(aStrokePrimitives) );
611
0
                        }
612
0
                    }
613
0
                }
614
0
            }
615
0
        }
616
617
0
        return aNewPrimitives;
618
0
    }
619
} // end of anonymous namespace
620
621
622
// primitive decomposition
623
624
void SdrTextObj::impDecomposePathTextPrimitive(
625
    drawinglayer::primitive2d::Primitive2DContainer& rTarget,
626
    const drawinglayer::primitive2d::SdrPathTextPrimitive2D& rSdrPathTextPrimitive,
627
    const drawinglayer::geometry::ViewInformation2D& aViewInformation) const
628
0
{
629
0
    drawinglayer::primitive2d::Primitive2DContainer aRetvalA;
630
0
    drawinglayer::primitive2d::Primitive2DContainer aRetvalB;
631
632
    // prepare outliner
633
0
    SdrOutliner& rOutliner = ImpGetDrawOutliner();
634
0
    rOutliner.SetUpdateLayout(true);
635
0
    rOutliner.Clear();
636
0
    rOutliner.SetPaperSize(Size(LONG_MAX,LONG_MAX));
637
0
    rOutliner.SetText(rSdrPathTextPrimitive.getOutlinerParaObject());
638
639
    // set visualizing page at Outliner; needed e.g. for PageNumberField decomposition
640
0
    rOutliner.setVisualizedPage(GetSdrPageFromXDrawPage(aViewInformation.getVisualizedPage()));
641
642
    // now break up to text portions
643
0
    TextHierarchyBreakupPathTextPortions aBreakup;
644
0
    rOutliner.StripPortions(aBreakup);
645
0
    const ::std::vector< impPathTextPortion > rPathTextPortions(aBreakup.sortAndGetPathTextPortions());
646
647
0
    if(!rPathTextPortions.empty())
648
0
    {
649
        // get FormText and polygon values
650
0
        const drawinglayer::attribute::SdrFormTextAttribute& rFormTextAttribute = rSdrPathTextPrimitive.getSdrFormTextAttribute();
651
0
        const basegfx::B2DPolyPolygon& rPathPolyPolygon(rSdrPathTextPrimitive.getPathPolyPolygon());
652
653
        // get loop count
654
0
        sal_uInt32 nLoopCount(rPathPolyPolygon.count());
655
656
0
        if(o3tl::make_unsigned(rOutliner.GetParagraphCount()) < nLoopCount)
657
0
        {
658
0
            nLoopCount = rOutliner.GetParagraphCount();
659
0
        }
660
661
0
        if(nLoopCount)
662
0
        {
663
            // prepare common decomposition stuff
664
0
            drawinglayer::primitive2d::Primitive2DContainer aRegularDecomposition;
665
0
            drawinglayer::primitive2d::Primitive2DContainer aShadowDecomposition;
666
0
            impPolygonParagraphHandler aPolygonParagraphHandler(
667
0
                rFormTextAttribute,
668
0
                aRegularDecomposition,
669
0
                aShadowDecomposition);
670
0
            sal_uInt32 a;
671
672
0
            for(a = 0; a < nLoopCount; a++)
673
0
            {
674
                // filter text portions for this paragraph
675
0
                ::std::vector< const impPathTextPortion* > aParagraphTextPortions;
676
677
0
                for(const auto & rCandidate : rPathTextPortions)
678
0
                {
679
0
                    if(static_cast<sal_uInt32>(rCandidate.getParagraph()) == a)
680
0
                    {
681
0
                        aParagraphTextPortions.push_back(&rCandidate);
682
0
                    }
683
0
                }
684
685
                // handle data pair polygon/ParagraphTextPortions
686
0
                if(!aParagraphTextPortions.empty())
687
0
                {
688
0
                    aPolygonParagraphHandler.HandlePair(rPathPolyPolygon.getB2DPolygon(a), aParagraphTextPortions);
689
0
                }
690
0
            }
691
692
0
            const sal_uInt32 nShadowCount(aShadowDecomposition.size());
693
0
            const sal_uInt32 nRegularCount(aRegularDecomposition.size());
694
695
0
            if(nShadowCount)
696
0
            {
697
                // add shadow primitives to decomposition
698
699
                // if necessary, add shadow outlines
700
0
                if(rFormTextAttribute.getFormTextOutline()
701
0
                    && !rFormTextAttribute.getShadowOutline().isDefault())
702
0
                {
703
0
                    aRetvalA = aShadowDecomposition;
704
0
                    const drawinglayer::primitive2d::Primitive2DContainer aOutlines(
705
0
                        impAddPathTextOutlines(
706
0
                            aShadowDecomposition,
707
0
                            rFormTextAttribute.getShadowOutline()));
708
709
0
                    aRetvalA.append(aOutlines);
710
0
                }
711
0
                else
712
0
                    aRetvalA = std::move(aShadowDecomposition);
713
0
            }
714
715
0
            if(nRegularCount)
716
0
            {
717
                // add normal primitives to decomposition
718
719
                // if necessary, add outlines
720
0
                if(rFormTextAttribute.getFormTextOutline()
721
0
                    && !rFormTextAttribute.getOutline().isDefault())
722
0
                {
723
0
                    aRetvalB = aRegularDecomposition;
724
0
                    const drawinglayer::primitive2d::Primitive2DContainer aOutlines(
725
0
                        impAddPathTextOutlines(
726
0
                            aRegularDecomposition,
727
0
                            rFormTextAttribute.getOutline()));
728
729
0
                    aRetvalB.append(aOutlines);
730
0
                }
731
0
                else
732
0
                    aRetvalB = std::move(aRegularDecomposition);
733
0
            }
734
0
        }
735
0
    }
736
737
    // clean up outliner
738
0
    rOutliner.Clear();
739
0
    rOutliner.setVisualizedPage(nullptr);
740
741
    // concatenate all results
742
0
    rTarget.append(std::move(aRetvalA));
743
0
    rTarget.append(std::move(aRetvalB));
744
0
}
745
746
747
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */