Coverage Report

Created: 2026-05-16 09:25

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/libreoffice/svx/source/customshapes/EnhancedCustomShapeFontWork.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 "EnhancedCustomShapeFontWork.hxx"
21
#include <svl/itemset.hxx>
22
#include <svx/compatflags.hxx>
23
#include <svx/svddef.hxx>
24
#include <svx/svdopath.hxx>
25
#include <vcl/kernarray.hxx>
26
#include <vcl/metric.hxx>
27
#include <vcl/rendercontext/AntialiasingFlags.hxx>
28
#include <svx/sdasitm.hxx>
29
#include <svx/sdtfsitm.hxx>
30
#include <vcl/virdev.hxx>
31
#include <svx/svditer.hxx>
32
#include <editeng/eeitem.hxx>
33
#include <editeng/frmdiritem.hxx>
34
#include <editeng/fontitem.hxx>
35
#include <editeng/postitem.hxx>
36
#include <editeng/wghtitem.hxx>
37
#include <editeng/fhgtitem.hxx>
38
#include <editeng/charscaleitem.hxx>
39
#include <svx/svdoashp.hxx>
40
#include <svx/sdshitm.hxx>
41
#include <svx/svdmodel.hxx>
42
#include <editeng/outlobj.hxx>
43
#include <editeng/editobj.hxx>
44
#include <o3tl/numeric.hxx>
45
#include <vector>
46
#include <numeric>
47
#include <algorithm>
48
#include <comphelper/processfactory.hxx>
49
#include <com/sun/star/i18n/BreakIterator.hpp>
50
#include <com/sun/star/i18n/ScriptType.hpp>
51
#include <basegfx/polygon/b2dpolypolygontools.hxx>
52
#include <basegfx/polygon/b2dpolygontools.hxx>
53
#include <sal/log.hxx>
54
#include <rtl/math.hxx>
55
#include <comphelper/configuration.hxx>
56
57
using namespace com::sun::star;
58
using namespace com::sun::star::uno;
59
60
namespace {
61
62
struct FWCharacterData                  // representing a single character
63
{
64
    std::vector< tools::PolyPolygon >   vOutlines;
65
    tools::Rectangle                           aBoundRect;
66
};
67
struct FWParagraphData                  // representing a single paragraph
68
{
69
    OUString                            aString;
70
    std::vector< FWCharacterData >      vCharacters;
71
    tools::Rectangle                           aBoundRect;
72
    SvxFrameDirection                   nFrameDirection;
73
};
74
struct FWTextArea                       // representing multiple concluding paragraphs
75
{
76
    std::vector< FWParagraphData >      vParagraphs;
77
    tools::Rectangle                           aBoundRect;
78
    sal_Int32                           nHAlignMove = 0;
79
};
80
struct FWData                           // representing the whole text
81
{
82
    std::vector< FWTextArea >           vTextAreas;
83
    double                              fHorizontalTextScaling;
84
    double                              fVerticalTextScaling;
85
    sal_uInt32                          nMaxParagraphsPerTextArea;
86
    sal_Int32                           nSingleLineHeight;
87
    bool                                bSingleLineMode;
88
    bool                                bScaleX;
89
};
90
91
}
92
93
static bool InitializeFontWorkData(
94
    const SdrObjCustomShape& rSdrObjCustomShape,
95
    const sal_uInt16 nOutlinesCount2d,
96
    FWData& rFWData)
97
0
{
98
0
    bool bNoErr = false;
99
0
    bool bSingleLineMode = false;
100
0
    sal_uInt16 nTextAreaCount = nOutlinesCount2d;
101
0
    if ( nOutlinesCount2d & 1 )
102
0
        bSingleLineMode = true;
103
0
    else
104
0
        nTextAreaCount >>= 1;
105
0
106
0
    const SdrCustomShapeGeometryItem& rGeometryItem( rSdrObjCustomShape.GetMergedItem( SDRATTR_CUSTOMSHAPE_GEOMETRY ) );
107
0
    const css::uno::Any* pAny = rGeometryItem.GetPropertyValueByName( u"TextPath"_ustr, u"ScaleX"_ustr );
108
0
    if (pAny)
109
0
        *pAny >>= rFWData.bScaleX;
110
0
    else
111
0
        rFWData.bScaleX = false;
112
0
113
0
    if ( nTextAreaCount )
114
0
    {
115
0
        rFWData.bSingleLineMode = bSingleLineMode;
116
0
117
0
        // setting the strings
118
0
        OutlinerParaObject* pParaObj(rSdrObjCustomShape.GetOutlinerParaObject());
119
0
120
0
        if ( pParaObj )
121
0
        {
122
0
            const EditTextObject& rTextObj = pParaObj->GetTextObject();
123
0
            sal_Int32 nParagraphsCount = rTextObj.GetParagraphCount();
124
0
125
0
            // Collect all the lines from all paragraphs
126
0
            std::vector<int> aLineParaID;      // which para this line is in
127
0
            std::vector<int> aLineStart;       // where this line start in that para
128
0
            std::vector<int> aLineLength;
129
0
            std::vector<OUString> aParaText;
130
0
            for (sal_Int32 nPara = 0; nPara < nParagraphsCount; ++nPara)
131
0
            {
132
0
                aParaText.push_back(rTextObj.GetText(nPara));
133
0
                sal_Int32 nPos = 0;
134
0
                sal_Int32 nPrevPos = 0;
135
0
                do
136
0
                {
137
0
                    // search line break.
138
0
                    if (!rSdrObjCustomShape.getSdrModelFromSdrObject().GetCompatibilityFlag(
139
0
                            SdrCompatibilityFlag::LegacyFontwork))
140
0
                        nPos = aParaText[nPara].indexOf(sal_Unicode(u'\1'), nPrevPos);
141
0
                    else
142
0
                        nPos = -1; // tdf#148000: ignore line breaks in legacy fontworks
143
0
144
0
                    aLineParaID.push_back(nPara);
145
0
                    aLineStart.push_back(nPrevPos);
146
0
                    aLineLength.push_back((nPos >= 0 ? nPos : aParaText[nPara].getLength())
147
0
                                          - nPrevPos);
148
0
                    nPrevPos = nPos + 1;
149
0
                } while (nPos >= 0);
150
0
            }
151
0
152
0
            sal_Int32 nLinesLeft = aLineParaID.size();
153
0
154
0
            rFWData.nMaxParagraphsPerTextArea = ((nLinesLeft - 1) / nTextAreaCount) + 1;
155
0
            sal_Int32 nLine = 0;
156
0
            while (nLinesLeft && nTextAreaCount)
157
0
            {
158
0
                FWTextArea aTextArea;
159
0
                sal_Int32 nLinesInPara = ((nLinesLeft - 1) / nTextAreaCount) + 1;
160
0
                for (sal_Int32 i = 0; i < nLinesInPara; ++i, ++nLine)
161
0
                {
162
0
                    FWParagraphData aParagraphData;
163
0
                    aParagraphData.aString = aParaText[aLineParaID[nLine]].subView(
164
0
                        aLineStart[nLine], aLineLength[nLine]);
165
0
166
0
                    // retrieving some paragraph attributes
167
0
                    const SfxItemSet& rParaSet = rTextObj.GetParaAttribs(aLineParaID[nLine]);
168
0
                    aParagraphData.nFrameDirection = rParaSet.Get(EE_PARA_WRITINGDIR).GetValue();
169
0
                    aTextArea.vParagraphs.push_back(std::move(aParagraphData));
170
0
                }
171
0
                rFWData.vTextAreas.push_back(std::move(aTextArea));
172
0
                nLinesLeft -= nLinesInPara;
173
0
                nTextAreaCount--;
174
0
            }
175
0
176
0
            bNoErr = true;
177
0
        }
178
0
    }
179
0
    return bNoErr;
180
0
}
181
182
static double GetLength( const tools::Polygon& rPolygon )
183
0
{
184
0
    double fLength = 0;
185
0
    if ( rPolygon.GetSize() > 1 )
186
0
    {
187
0
        sal_uInt16 nCount = rPolygon.GetSize();
188
0
        while( --nCount )
189
0
            fLength += rPolygon.CalcDistance( nCount, nCount - 1 );
190
0
    }
191
0
    return fLength;
192
0
}
193
194
195
/* CalculateHorizontalScalingFactor returns the horizontal scaling factor for
196
the whole text object, so that each text will match its corresponding 2d Outline */
197
static void CalculateHorizontalScalingFactor(
198
    const SdrObjCustomShape& rSdrObjCustomShape,
199
    FWData& rFWData,
200
    const tools::PolyPolygon& rOutline2d)
201
0
{
202
0
    double fScalingFactor = 1.0;
203
0
    rFWData.fVerticalTextScaling = 1.0;
204
0
205
0
    sal_uInt16 i = 0;
206
0
    bool bSingleLineMode = false;
207
0
    sal_uInt16 nOutlinesCount2d = rOutline2d.Count();
208
0
209
0
    vcl::Font aFont;
210
0
    const SvxFontItem& rFontItem( rSdrObjCustomShape.GetMergedItem( EE_CHAR_FONTINFO ) );
211
0
    const SvxFontHeightItem& rFontHeight( rSdrObjCustomShape.GetMergedItem( EE_CHAR_FONTHEIGHT ) );
212
0
    sal_Int32 nFontSize = rFontHeight.GetHeight();
213
0
214
0
    if (rFWData.bScaleX)
215
0
        aFont.SetFontHeight( nFontSize );
216
0
    else
217
0
        aFont.SetFontHeight( rSdrObjCustomShape.GetLogicRect().GetHeight() / rFWData.nMaxParagraphsPerTextArea );
218
0
219
0
    aFont.SetAlignment( ALIGN_TOP );
220
0
    aFont.SetFamilyName( rFontItem.GetFamilyName() );
221
0
    aFont.SetFamily( rFontItem.GetFamily() );
222
0
    aFont.SetStyleName( rFontItem.GetStyleName() );
223
0
    const SvxPostureItem& rPostureItem = rSdrObjCustomShape.GetMergedItem( EE_CHAR_ITALIC );
224
0
    aFont.SetItalic( rPostureItem.GetPosture() );
225
0
226
0
    const SvxWeightItem& rWeightItem = rSdrObjCustomShape.GetMergedItem( EE_CHAR_WEIGHT );
227
0
    aFont.SetWeight( rWeightItem.GetWeight() );
228
0
    aFont.SetOrientation( 0_deg10 );
229
0
    // initializing virtual device
230
0
231
0
    ScopedVclPtrInstance< VirtualDevice > pVirDev(DeviceFormat::WITHOUT_ALPHA);
232
0
    pVirDev->SetMapMode(MapMode(MapUnit::Map100thMM));
233
0
    pVirDev->SetFont( aFont );
234
0
    pVirDev->SetAntialiasing( AntialiasingFlags::DisableText );
235
0
236
0
    if ( nOutlinesCount2d & 1 )
237
0
        bSingleLineMode = true;
238
0
239
0
    // In case of rFWData.bScaleX == true it loops with reduced font size until the current run
240
0
    // results in a fScalingFactor >=1.0. The fact, that case rFWData.bScaleX == true keeps font
241
0
    // size if possible, is not done here with scaling factor 1 but is done in method
242
0
    // FitTextOutlinesToShapeOutlines()
243
0
    do
244
0
    {
245
0
        i = 0;
246
0
        bool bScalingFactorDefined = false; // New calculation for each font size
247
0
        for( const auto& rTextArea : rFWData.vTextAreas )
248
0
        {
249
0
            // calculating the width of the corresponding 2d text area
250
0
            double fWidth = GetLength( rOutline2d.GetObject( i++ ) );
251
0
            if ( !bSingleLineMode )
252
0
            {
253
0
                fWidth += GetLength( rOutline2d.GetObject( i++ ) );
254
0
                fWidth /= 2.0;
255
0
            }
256
0
257
0
            for( const auto& rParagraph : rTextArea.vParagraphs )
258
0
            {
259
0
                double fTextWidth = pVirDev->GetTextWidth( rParagraph.aString );
260
0
                if ( fTextWidth > 0.0 )
261
0
                {
262
0
                    double fScale = fWidth / fTextWidth;
263
0
                    if ( !bScalingFactorDefined )
264
0
                    {
265
0
                        fScalingFactor = fScale;
266
0
                        bScalingFactorDefined = true;
267
0
                    }
268
0
                    else if (fScale < fScalingFactor)
269
0
                    {
270
0
                        fScalingFactor = fScale;
271
0
                    }
272
0
                }
273
0
            }
274
0
        }
275
0
276
0
        if (fScalingFactor < 1.0)
277
0
        {
278
0
            nFontSize--;
279
0
            aFont.SetFontHeight( nFontSize );
280
0
            pVirDev->SetFont( aFont );
281
0
        }
282
0
    }
283
0
    while (rFWData.bScaleX && fScalingFactor < 1.0 && nFontSize > 1 );
284
0
285
0
    if (nFontSize > 1)
286
0
        rFWData.fVerticalTextScaling = static_cast<double>(nFontSize) / rFontHeight.GetHeight();
287
0
288
0
    rFWData.fHorizontalTextScaling = fScalingFactor;
289
0
}
290
291
static void GetTextAreaOutline(
292
    const FWData& rFWData,
293
    const SdrObjCustomShape& rSdrObjCustomShape,
294
    FWTextArea& rTextArea,
295
    bool bSameLetterHeights)
296
0
{
297
0
    bool bIsVertical(rSdrObjCustomShape.IsVerticalWriting());
298
0
    sal_Int32 nVerticalOffset = rFWData.nMaxParagraphsPerTextArea > rTextArea.vParagraphs.size()
299
0
                                    ? rFWData.nSingleLineHeight / 2 : 0;
300
0
301
0
    for( auto& rParagraph : rTextArea.vParagraphs )
302
0
    {
303
0
        const OUString& rText = rParagraph.aString;
304
0
        if ( !rText.isEmpty() )
305
0
        {
306
0
            // generating vcl/font
307
0
            sal_uInt16 nScriptType = i18n::ScriptType::LATIN;
308
0
            Reference< i18n::XBreakIterator > xBI( EnhancedCustomShapeFontWork::GetBreakIterator() );
309
0
            if ( xBI.is() )
310
0
            {
311
0
                nScriptType = xBI->getScriptType( rText, 0 );
312
0
                if( i18n::ScriptType::WEAK == nScriptType )
313
0
                {
314
0
                    sal_Int32 nChg = xBI->endOfScript( rText, 0, nScriptType );
315
0
                    if (nChg < rText.getLength() && nChg >= 0)
316
0
                        nScriptType = xBI->getScriptType( rText, nChg );
317
0
                    else
318
0
                        nScriptType = i18n::ScriptType::LATIN;
319
0
                }
320
0
            }
321
0
            sal_uInt16 nFntItm = EE_CHAR_FONTINFO;
322
0
            if ( nScriptType == i18n::ScriptType::COMPLEX )
323
0
                nFntItm = EE_CHAR_FONTINFO_CTL;
324
0
            else if ( nScriptType == i18n::ScriptType::ASIAN )
325
0
                nFntItm = EE_CHAR_FONTINFO_CJK;
326
0
            const SvxFontItem& rFontItem = static_cast<const SvxFontItem&>(rSdrObjCustomShape.GetMergedItem( nFntItm ));
327
0
            vcl::Font aFont;
328
0
329
0
            aFont.SetFontHeight( rFWData.nSingleLineHeight );
330
0
331
0
            aFont.SetAlignment( ALIGN_TOP );
332
0
333
0
            aFont.SetFamilyName( rFontItem.GetFamilyName() );
334
0
            aFont.SetFamily( rFontItem.GetFamily() );
335
0
            aFont.SetStyleName( rFontItem.GetStyleName() );
336
0
            aFont.SetOrientation( 0_deg10 );
337
0
338
0
            const SvxPostureItem& rPostureItem = rSdrObjCustomShape.GetMergedItem( EE_CHAR_ITALIC );
339
0
            aFont.SetItalic( rPostureItem.GetPosture() );
340
0
341
0
            const SvxWeightItem& rWeightItem = rSdrObjCustomShape.GetMergedItem( EE_CHAR_WEIGHT );
342
0
            aFont.SetWeight( rWeightItem.GetWeight() );
343
0
344
0
            // initializing virtual device
345
0
            ScopedVclPtrInstance< VirtualDevice > pVirDev(DeviceFormat::WITHOUT_ALPHA);
346
0
            pVirDev->SetMapMode(MapMode(MapUnit::Map100thMM));
347
0
            pVirDev->SetFont( aFont );
348
0
            pVirDev->SetAntialiasing( AntialiasingFlags::DisableText );
349
0
350
0
            pVirDev->EnableRTL();
351
0
            if ( rParagraph.nFrameDirection == SvxFrameDirection::Horizontal_RL_TB )
352
0
                pVirDev->SetLayoutMode( vcl::text::ComplexTextLayoutFlags::BiDiRtl );
353
0
354
0
            const SvxCharScaleWidthItem& rCharScaleWidthItem = rSdrObjCustomShape.GetMergedItem( EE_CHAR_FONTWIDTH );
355
0
            sal_uInt16 nCharScaleWidth = rCharScaleWidthItem.GetValue();
356
0
            sal_Int32 nWidth = 0;
357
0
358
0
            // VERTICAL
359
0
            if ( bIsVertical )
360
0
            {
361
0
                // vertical _> each single character needs to be rotated by 90
362
0
                sal_Int32 i;
363
0
                sal_Int32 nHeight = 0;
364
0
                tools::Rectangle aSingleCharacterUnion;
365
0
                for ( i = 0; i < rText.getLength(); i++ )
366
0
                {
367
0
                    FWCharacterData aCharacterData;
368
0
                    OUString aCharText( rText[ i ] );
369
0
                    if ( pVirDev->GetTextOutlines( aCharacterData.vOutlines, aCharText, 0, 0, -1, nWidth, {} ) )
370
0
                    {
371
0
                        sal_Int32 nTextWidth = pVirDev->GetTextWidth( aCharText);
372
0
                        if ( aCharacterData.vOutlines.empty() )
373
0
                        {
374
0
                            nHeight += rFWData.nSingleLineHeight;
375
0
                        }
376
0
                        else
377
0
                        {
378
0
                            for ( auto& rOutline : aCharacterData.vOutlines )
379
0
                            {
380
0
                                // rotating
381
0
                                rOutline.Rotate( Point( nTextWidth / 2, rFWData.nSingleLineHeight / 2 ), 900_deg10 );
382
0
                                aCharacterData.aBoundRect.Union( rOutline.GetBoundRect() );
383
0
                            }
384
0
                            for ( auto& rOutline : aCharacterData.vOutlines )
385
0
                            {
386
0
                                sal_Int32 nM = - aCharacterData.aBoundRect.Left() + nHeight;
387
0
                                rOutline.Move( nM, 0 );
388
0
                                aCharacterData.aBoundRect.Move( nM, 0 );
389
0
                            }
390
0
                            nHeight += aCharacterData.aBoundRect.GetWidth() + ( rFWData.nSingleLineHeight / 5 );
391
0
                            aSingleCharacterUnion.Union( aCharacterData.aBoundRect );
392
0
                        }
393
0
                    }
394
0
                    rParagraph.vCharacters.push_back(std::move(aCharacterData));
395
0
                }
396
0
                for ( auto& rCharacter : rParagraph.vCharacters )
397
0
                {
398
0
                    for ( auto& rOutline : rCharacter.vOutlines )
399
0
                    {
400
0
                        rOutline.Move( ( aSingleCharacterUnion.GetWidth() - rCharacter.aBoundRect.GetWidth() ) / 2, 0 );
401
0
                    }
402
0
                }
403
0
            }
404
0
            else
405
0
            {
406
0
                KernArray aDXArray;
407
0
                if ( ( nCharScaleWidth != 100 ) && nCharScaleWidth )
408
0
                {   // applying character spacing
409
0
                    pVirDev->GetTextArray( rText, &aDXArray);
410
0
                    FontMetric aFontMetric( pVirDev->GetFontMetric() );
411
0
                    aFont.SetAverageFontWidth( static_cast<sal_Int32>( static_cast<double>(aFontMetric.GetAverageFontWidth()) * ( double(100) / static_cast<double>(nCharScaleWidth) ) ) );
412
0
                    pVirDev->SetFont( aFont );
413
0
                }
414
0
                FWCharacterData aCharacterData;
415
0
                if ( pVirDev->GetTextOutlines( aCharacterData.vOutlines, rText, 0, 0, -1, nWidth, aDXArray ) )
416
0
                {
417
0
                    rParagraph.vCharacters.push_back(std::move(aCharacterData));
418
0
                }
419
0
                else
420
0
                {
421
0
                    // GetTextOutlines failed what usually means that it is
422
0
                    // not implemented. To make FontWork not fail (it is
423
0
                    // dependent of graphic content to get a Range) create
424
0
                    // a rectangle substitution for now
425
0
                    pVirDev->GetTextArray( rText, &aDXArray);
426
0
                    aCharacterData.vOutlines.clear();
427
0
428
0
                    if(!aDXArray.empty())
429
0
                    {
430
0
                        for(size_t a(0); a < aDXArray.size(); a++)
431
0
                        {
432
0
                            const basegfx::B2DPolygon aPolygon(
433
0
                                basegfx::utils::createPolygonFromRect(
434
0
                                basegfx::B2DRange(
435
0
                                    0 == a ? 0 : aDXArray[a - 1],
436
0
                                    0,
437
0
                                    aDXArray[a],
438
0
                                    aFont.GetFontHeight()
439
0
                                )));
440
0
                            aCharacterData.vOutlines.emplace_back(tools::Polygon(aPolygon));
441
0
                        }
442
0
                    }
443
0
                    else
444
0
                    {
445
0
                        const basegfx::B2DPolygon aPolygon(
446
0
                            basegfx::utils::createPolygonFromRect(
447
0
                            basegfx::B2DRange(
448
0
                                0,
449
0
                                0,
450
0
                                10,
451
0
                                aFont.GetFontHeight()
452
0
                            )));
453
0
                        aCharacterData.vOutlines.emplace_back(tools::Polygon(aPolygon));
454
0
                    }
455
0
456
0
457
0
                    rParagraph.vCharacters.push_back(std::move(aCharacterData));
458
0
                }
459
0
            }
460
0
461
0
            // vertical alignment
462
0
            for ( auto& rCharacter : rParagraph.vCharacters )
463
0
            {
464
0
                for( tools::PolyPolygon& rPolyPoly : rCharacter.vOutlines )
465
0
                {
466
0
                    if ( nVerticalOffset )
467
0
                        rPolyPoly.Move( 0, nVerticalOffset );
468
0
469
0
                    // retrieving the boundrect for the paragraph
470
0
                    tools::Rectangle aBoundRect( rPolyPoly.GetBoundRect() );
471
0
                    rParagraph.aBoundRect.Union( aBoundRect );
472
0
                }
473
0
            }
474
0
        }
475
0
        // updating the boundrect for the text area by merging the current paragraph boundrect
476
0
        if ( rParagraph.aBoundRect.IsEmpty() )
477
0
        {
478
0
            if ( rTextArea.aBoundRect.IsEmpty() )
479
0
                rTextArea.aBoundRect = tools::Rectangle( Point( 0, 0 ), Size( 1, rFWData.nSingleLineHeight ) );
480
0
            else
481
0
                rTextArea.aBoundRect.AdjustBottom(rFWData.nSingleLineHeight );
482
0
        }
483
0
        else
484
0
        {
485
0
            tools::Rectangle& rParagraphBoundRect = rParagraph.aBoundRect;
486
0
            rTextArea.aBoundRect.Union( rParagraphBoundRect );
487
0
488
0
            if ( bSameLetterHeights )
489
0
            {
490
0
                for ( auto& rCharacter : rParagraph.vCharacters )
491
0
                {
492
0
                    for( auto& rOutline : rCharacter.vOutlines )
493
0
                    {
494
0
                        tools::Rectangle aPolyPolyBoundRect( rOutline.GetBoundRect() );
495
0
                        if (aPolyPolyBoundRect.GetHeight() != rParagraphBoundRect.GetHeight() && aPolyPolyBoundRect.GetHeight())
496
0
                            rOutline.Scale( 1.0, static_cast<double>(rParagraphBoundRect.GetHeight()) / aPolyPolyBoundRect.GetHeight() );
497
0
                        aPolyPolyBoundRect = rOutline.GetBoundRect();
498
0
                        sal_Int32 nMove = aPolyPolyBoundRect.Top() - rParagraphBoundRect.Top();
499
0
                        if ( nMove )
500
0
                            rOutline.Move( 0, -nMove );
501
0
                    }
502
0
                }
503
0
            }
504
0
        }
505
0
        if ( bIsVertical )
506
0
            nVerticalOffset -= rFWData.nSingleLineHeight;
507
0
        else
508
0
            nVerticalOffset += rFWData.nSingleLineHeight;
509
0
    }
510
0
}
511
512
static bool GetFontWorkOutline(
513
    FWData& rFWData,
514
    const SdrObjCustomShape& rSdrObjCustomShape)
515
0
{
516
0
    SdrTextHorzAdjust eHorzAdjust(rSdrObjCustomShape.GetMergedItem( SDRATTR_TEXT_HORZADJUST ).GetValue());
517
0
    drawing::TextFitToSizeType const eFTS(rSdrObjCustomShape.GetMergedItem( SDRATTR_TEXT_FITTOSIZE ).GetValue());
518
0
519
0
    bool bSameLetterHeights = false;
520
0
    const SdrCustomShapeGeometryItem& rGeometryItem(rSdrObjCustomShape.GetMergedItem( SDRATTR_CUSTOMSHAPE_GEOMETRY ));
521
0
    const css::uno::Any* pAny = rGeometryItem.GetPropertyValueByName( u"TextPath"_ustr, u"SameLetterHeights"_ustr );
522
0
    if ( pAny )
523
0
        *pAny >>= bSameLetterHeights;
524
0
525
0
    const SvxFontHeightItem& rFontHeight( rSdrObjCustomShape.GetMergedItem( EE_CHAR_FONTHEIGHT ) );
526
0
    if (rFWData.bScaleX)
527
0
        rFWData.nSingleLineHeight = rFWData.fVerticalTextScaling * rFontHeight.GetHeight();
528
0
    else
529
0
        rFWData.nSingleLineHeight = static_cast<sal_Int32>( ( static_cast<double>( rSdrObjCustomShape.GetLogicRect().GetHeight() )
530
0
                                                    / rFWData.nMaxParagraphsPerTextArea ) * rFWData.fHorizontalTextScaling );
531
0
532
0
    if (rFWData.nSingleLineHeight == SAL_MIN_INT32)
533
0
        return false;
534
0
535
0
    for ( auto& rTextArea : rFWData.vTextAreas )
536
0
    {
537
0
        GetTextAreaOutline(
538
0
            rFWData,
539
0
            rSdrObjCustomShape,
540
0
            rTextArea,
541
0
            bSameLetterHeights);
542
0
543
0
        if (eFTS == drawing::TextFitToSizeType_ALLLINES ||
544
0
            // tdf#97630 interpret PROPORTIONAL same as ALLLINES so we don't
545
0
            // need another ODF attribute!
546
0
            eFTS == drawing::TextFitToSizeType_PROPORTIONAL)
547
0
        {
548
0
            for ( auto& rParagraph : rTextArea.vParagraphs )
549
0
            {
550
0
                sal_Int32 nParaWidth = rParagraph.aBoundRect.GetWidth();
551
0
                if ( nParaWidth )
552
0
                {
553
0
                    double fScale = static_cast<double>(rTextArea.aBoundRect.GetWidth()) / nParaWidth;
554
0
555
0
                    for ( auto& rCharacter : rParagraph.vCharacters )
556
0
                    {
557
0
                        for( auto& rOutline : rCharacter.vOutlines )
558
0
                        {
559
0
                            rOutline.Scale( fScale, 1.0 );
560
0
                        }
561
0
                    }
562
0
                }
563
0
            }
564
0
        }
565
0
        else if (rFWData.bScaleX)
566
0
        {
567
0
            const SdrTextVertAdjust nVertJustify = rSdrObjCustomShape.GetMergedItem( SDRATTR_TEXT_VERTADJUST ).GetValue();
568
0
            double fFactor = nVertJustify == SdrTextVertAdjust::SDRTEXTVERTADJUST_BOTTOM ? -0.5 : ( nVertJustify == SdrTextVertAdjust::SDRTEXTVERTADJUST_TOP ? 0.5 : 0 );
569
0
570
0
            for ( auto& rParagraph : rTextArea.vParagraphs )
571
0
            {
572
0
                sal_Int32 nHorzDiff = 0;
573
0
                sal_Int32 nVertDiff = static_cast<double>( rFWData.nSingleLineHeight ) * fFactor * ( rTextArea.vParagraphs.size() - 1 );
574
0
                rTextArea.nHAlignMove = nVertDiff;
575
0
576
0
                if ( eHorzAdjust == SDRTEXTHORZADJUST_CENTER )
577
0
                    nHorzDiff = ( rFWData.fHorizontalTextScaling * rTextArea.aBoundRect.GetWidth() - rParagraph.aBoundRect.GetWidth() ) / 2;
578
0
                else if ( eHorzAdjust == SDRTEXTHORZADJUST_RIGHT )
579
0
                    nHorzDiff = ( rFWData.fHorizontalTextScaling * rTextArea.aBoundRect.GetWidth() - rParagraph.aBoundRect.GetWidth() );
580
0
581
0
                if (nHorzDiff || nVertDiff)
582
0
                {
583
0
                    for ( auto& rCharacter : rParagraph.vCharacters )
584
0
                    {
585
0
                        for( auto& rOutline : rCharacter.vOutlines )
586
0
                        {
587
0
                            rOutline.Move( nHorzDiff, nVertDiff );
588
0
                        }
589
0
                    }
590
0
                }
591
0
            }
592
0
        }
593
0
        else
594
0
        {
595
0
            switch( eHorzAdjust )
596
0
            {
597
0
                case SDRTEXTHORZADJUST_RIGHT :
598
0
                case SDRTEXTHORZADJUST_CENTER:
599
0
                {
600
0
                    for ( auto& rParagraph : rTextArea.vParagraphs )
601
0
                    {
602
0
                        sal_Int32 nHorzDiff = 0;
603
0
                        if ( eHorzAdjust == SDRTEXTHORZADJUST_CENTER )
604
0
                            nHorzDiff = ( rTextArea.aBoundRect.GetWidth() - rParagraph.aBoundRect.GetWidth() ) / 2;
605
0
                        else if ( eHorzAdjust == SDRTEXTHORZADJUST_RIGHT )
606
0
                            nHorzDiff = ( rTextArea.aBoundRect.GetWidth() - rParagraph.aBoundRect.GetWidth() );
607
0
                        if ( nHorzDiff )
608
0
                        {
609
0
                            for ( auto& rCharacter : rParagraph.vCharacters )
610
0
                            {
611
0
                                for( auto& rOutline : rCharacter.vOutlines )
612
0
                                {
613
0
                                    rOutline.Move( nHorzDiff, 0 );
614
0
                                }
615
0
                            }
616
0
                        }
617
0
                    }
618
0
                }
619
0
                break;
620
0
                default:
621
0
                case SDRTEXTHORZADJUST_BLOCK : break;   // don't know
622
0
                case SDRTEXTHORZADJUST_LEFT : break;    // already left aligned -> nothing to do
623
0
            }
624
0
        }
625
0
    }
626
0
627
0
    return true;
628
0
}
629
630
static basegfx::B2DPolyPolygon GetOutlinesFromShape2d( const SdrObject* pShape2d )
631
0
{
632
0
    basegfx::B2DPolyPolygon aOutlines2d;
633
0
634
0
    SdrObjListIter aObjListIter( *pShape2d, SdrIterMode::DeepWithGroups );
635
0
    while( aObjListIter.IsMore() )
636
0
    {
637
0
        SdrObject* pPartObj = aObjListIter.Next();
638
0
        if ( auto pPathObj = dynamic_cast<const SdrPathObj*>( pPartObj))
639
0
        {
640
0
            basegfx::B2DPolyPolygon aCandidate(pPathObj->GetPathPoly());
641
0
            if(aCandidate.areControlPointsUsed())
642
0
            {
643
0
                aCandidate = basegfx::utils::adaptiveSubdivideByAngle(aCandidate);
644
0
            }
645
0
            aOutlines2d.append(aCandidate);
646
0
        }
647
0
    }
648
0
649
0
    return aOutlines2d;
650
0
}
651
652
static void CalcDistances( const tools::Polygon& rPoly, std::vector< double >& rDistances )
653
0
{
654
0
    sal_uInt16 i, nCount = rPoly.GetSize();
655
0
    if ( nCount <= 1 )
656
0
        return;
657
0
658
0
    for ( i = 0; i < nCount; i++ )
659
0
    {
660
0
        double fDistance = i ? rPoly.CalcDistance( i, i - 1 ) : 0.0;
661
0
        rDistances.push_back( fDistance );
662
0
    }
663
0
    std::partial_sum( rDistances.begin(), rDistances.end(), rDistances.begin() );
664
0
    double fLength = rDistances[ rDistances.size() - 1 ];
665
0
    if ( fLength > 0.0 )
666
0
    {
667
0
        for ( auto& rDistance : rDistances )
668
0
            rDistance /= fLength;
669
0
    }
670
0
}
671
672
static void InsertMissingOutlinePoints( const std::vector< double >& rDistances,
673
                                 const tools::Rectangle& rTextAreaBoundRect, tools::Polygon& rPoly )
674
0
{
675
0
    sal_uInt16 nSize = rPoly.GetSize();
676
0
    if (nSize == 0)
677
0
        return;
678
0
679
0
    tools::Long nTextWidth = rTextAreaBoundRect.GetWidth();
680
0
681
0
    if (nTextWidth == 0)
682
0
        throw o3tl::divide_by_zero();
683
0
684
0
    double fLastDistance = 0.0;
685
0
    for (sal_uInt16 i = 0; i < nSize; ++i)
686
0
    {
687
0
        Point& rPoint = rPoly[ i ];
688
0
        double fDistance = static_cast<double>( rPoint.X() - rTextAreaBoundRect.Left() ) / static_cast<double>(nTextWidth);
689
0
        if ( i )
690
0
        {
691
0
            if ( fDistance > fLastDistance )
692
0
            {
693
0
                std::vector< double >::const_iterator aIter = std::upper_bound( rDistances.begin(), rDistances.end(), fLastDistance );
694
0
                if  ( aIter != rDistances.end() && ( *aIter > fLastDistance ) && ( *aIter < fDistance ) )
695
0
                {
696
0
                    Point& rPt0 = rPoly[ i - 1 ];
697
0
                    sal_Int32 fX = rPoint.X() - rPt0.X();
698
0
                    sal_Int32 fY = rPoint.Y() - rPt0.Y();
699
0
                    double fd = ( 1.0 / ( fDistance - fLastDistance ) ) * ( *aIter - fLastDistance );
700
0
                    rPoly.Insert( i, Point( static_cast<sal_Int32>( rPt0.X() + fX * fd ), static_cast<sal_Int32>( rPt0.Y() + fY * fd ) ) );
701
0
                    fDistance = *aIter;
702
0
                }
703
0
            }
704
0
            else if ( fDistance < fLastDistance )
705
0
            {
706
0
                std::vector< double >::const_iterator aIter = std::lower_bound( rDistances.begin(), rDistances.end(), fLastDistance );
707
0
                if  ( aIter != rDistances.begin() )
708
0
                {
709
0
                    --aIter;
710
0
                    if ( ( *aIter > fDistance ) && ( *aIter < fLastDistance ) )
711
0
                    {
712
0
                        Point& rPt0 = rPoly[ i - 1 ];
713
0
                        sal_Int32 fX = rPoint.X() - rPt0.X();
714
0
                        sal_Int32 fY = rPoint.Y() - rPt0.Y();
715
0
                        double fd = ( 1.0 / ( fDistance - fLastDistance ) ) * ( *aIter - fLastDistance );
716
0
                        rPoly.Insert( i, Point( static_cast<sal_Int32>( rPt0.X() + fX * fd ), static_cast<sal_Int32>( rPt0.Y() + fY * fd ) ) );
717
0
                        fDistance = *aIter;
718
0
                    }
719
0
                }
720
0
            }
721
0
        }
722
0
        fLastDistance = fDistance;
723
0
    }
724
0
}
725
726
//only 2 types used: 'const tools::Polygon&' and 'const std::vector<Point>&'
727
template <class T>
728
static void GetPoint( T rPoly, const std::vector< double >& rDistances, const double& fX, double& fx1, double& fy1 )
729
0
{
730
0
    fy1 = fx1 = 0.0;
731
0
    if (rPoly.size() <= 1)
732
0
        return;
733
0
734
0
    std::vector< double >::const_iterator aIter = std::lower_bound( rDistances.begin(), rDistances.end(), fX );
735
0
    sal_uInt16 nIdx = sal::static_int_cast<sal_uInt16>( std::distance( rDistances.begin(), aIter ) );
736
0
    if ( aIter == rDistances.end() )
737
0
        nIdx--;
738
0
    const Point& rPt = rPoly[ nIdx ];
739
0
    fx1 = rPt.X();
740
0
    fy1 = rPt.Y();
741
0
    if ( !nIdx || ( aIter == rDistances.end() ) || rtl::math::approxEqual( *aIter, fX ) )
742
0
        return;
743
0
744
0
    nIdx = sal::static_int_cast<sal_uInt16>( std::distance( rDistances.begin(), aIter ) );
745
0
    double fDist0 = *( aIter - 1 );
746
0
    double fd = ( 1.0 / ( *aIter - fDist0 ) ) * ( fX - fDist0 );
747
0
    const Point& rPt2 = rPoly[ nIdx - 1 ];
748
0
    double fWidth = rPt.X() - rPt2.X();
749
0
    double fHeight= rPt.Y() - rPt2.Y();
750
0
    fWidth *= fd;
751
0
    fHeight*= fd;
752
0
    fx1 = rPt2.X() + fWidth;
753
0
    fy1 = rPt2.Y() + fHeight;
754
0
}
Unexecuted instantiation: EnhancedCustomShapeFontWork.cxx:void GetPoint<std::__1::vector<Point, std::__1::allocator<Point> > >(std::__1::vector<Point, std::__1::allocator<Point> >, std::__1::vector<double, std::__1::allocator<double> > const&, double const&, double&, double&)
Unexecuted instantiation: EnhancedCustomShapeFontWork.cxx:void GetPoint<tools::Polygon>(tools::Polygon, std::__1::vector<double, std::__1::allocator<double> > const&, double const&, double&, double&)
755
756
static void FitTextOutlinesToShapeOutlines(const tools::PolyPolygon& aOutlines2d, FWData& rFWData,
757
                                           SdrTextHorzAdjust eHorzAdjust, bool bPPFontwork)
758
0
{
759
0
    sal_uInt16 nOutline2dIdx = 0;
760
0
    for( auto& rTextArea : rFWData.vTextAreas )
761
0
    {
762
0
        tools::Rectangle rTextAreaBoundRect = rTextArea.aBoundRect;
763
0
        sal_Int32 nLeft = rTextAreaBoundRect.Left();
764
0
        sal_Int32 nTop = rTextAreaBoundRect.Top();
765
0
        sal_Int32 nWidth = rTextAreaBoundRect.GetWidth();
766
0
        sal_Int32 nHeight= rTextAreaBoundRect.GetHeight();
767
0
768
0
        if (rFWData.bScaleX)
769
0
        {
770
0
            nWidth *= rFWData.fHorizontalTextScaling;
771
0
        }
772
0
773
0
        if ( rFWData.bSingleLineMode && nHeight && nWidth )
774
0
        {
775
0
            if ( nOutline2dIdx >= aOutlines2d.Count() )
776
0
                break;
777
0
            const tools::Polygon& rOutlinePoly( aOutlines2d[ nOutline2dIdx++ ] );
778
0
            const sal_uInt16 nPointCount = rOutlinePoly.GetSize();
779
0
            if ( nPointCount > 1 )
780
0
            {
781
0
                std::vector< double > vDistances;
782
0
                vDistances.reserve( nPointCount );
783
0
                CalcDistances( rOutlinePoly, vDistances );
784
0
785
0
                if ( !vDistances.empty() )
786
0
                {
787
0
                    // horizontal alignment: how much we have to move text to the right.
788
0
                    int nAdjust = -1;
789
0
                    switch (eHorzAdjust)
790
0
                    {
791
0
                        case SDRTEXTHORZADJUST_RIGHT:
792
0
                            nAdjust = 2; // 2 half of the possible
793
0
                            break;
794
0
                        case SDRTEXTHORZADJUST_CENTER:
795
0
                            nAdjust = 1; // 1 half of the possible
796
0
                            break;
797
0
                        case SDRTEXTHORZADJUST_BLOCK:
798
0
                            nAdjust = -1; // don't know what it is, so don't even align
799
0
                            break;
800
0
                        case SDRTEXTHORZADJUST_LEFT:
801
0
                            nAdjust = 0; // no need to move
802
0
                            break;
803
0
                    }
804
0
805
0
                    if (bPPFontwork && rTextArea.vParagraphs.size() > 1 && nAdjust >= 0)
806
0
                    {
807
0
                        // If we have multiple lines of text to fit to the outline (curve)
808
0
                        // then we have to be able to calculate outer versions of the outline
809
0
                        // where we can fit the next lines of texts
810
0
                        // those outer lines will be wider (or shorter) as the original outline
811
0
                        // and probably will looks different as the original outline.
812
0
                        //
813
0
                        // for example if we have an outline like this:
814
0
                        // <____>
815
0
                        // then the middle part will have the same normals, so distances there,
816
0
                        //  will not change for an outer outline
817
0
                        // while the points near the edge will have different normals,
818
0
                        //  distances around there will increase for an outer (wider) outline
819
0
820
0
                        //Normal vectors for every rOutlinePoly point. 1024 long
821
0
                        std::vector<Point> vNorm;
822
0
                        //wider curve path points, for current paragraph (rOutlinePoly + vNorm*line)
823
0
                        std::vector<Point> vCurOutline;
824
0
                        //distances between points of this wider curve
825
0
                        std::vector<double> vCurDistances;
826
0
827
0
                        vCurDistances.reserve(nPointCount);
828
0
                        vCurOutline.reserve(nPointCount);
829
0
                        vNorm.reserve(nPointCount);
830
0
831
0
                        // Calculate Normal vectors, and allocate curve data
832
0
                        sal_uInt16 i;
833
0
                        for (i = 0; i < nPointCount; i++)
834
0
                        {
835
0
                            //Normal vector for a point will be calculated from its neighbour points
836
0
                            //except if it is in the start/end of the vector
837
0
                            sal_uInt16 nPointIdx1 = i == 0 ? i : i - 1;
838
0
                            sal_uInt16 nPointIdx2 = i == nPointCount - 1 ? i : i + 1;
839
0
840
0
                            Point aPoint = rOutlinePoly.GetPoint(nPointIdx2)
841
0
                                           - rOutlinePoly.GetPoint(nPointIdx1);
842
0
843
0
                            double fLen = hypot(aPoint.X(), aPoint.Y());
844
0
845
0
                            if (fLen > 0)
846
0
                            {
847
0
                                //Rotate by 90 degree, and divide by length, to get normal vector
848
0
                                vNorm.emplace_back(aPoint.getY() * 1024 / fLen,
849
0
                                                   -aPoint.getX() * 1024 / fLen);
850
0
                            }
851
0
                            else
852
0
                            {
853
0
                                vNorm.emplace_back(0, 0);
854
0
                            }
855
0
                            vCurOutline.emplace_back(Point());
856
0
                            vCurDistances.push_back(0);
857
0
858
0
                        }
859
0
860
0
                        for( auto& rParagraph : rTextArea.vParagraphs )
861
0
                        {
862
0
                            //calculate the actual outline length, and its align adjustments
863
0
                            double fAdjust;
864
0
                            double fCurWidth;
865
0
866
0
                            // distance between the original and the current curve
867
0
                            double fCurvesDist = rTextArea.aBoundRect.GetHeight() / 2.0
868
0
                                                 + rTextArea.aBoundRect.Top()
869
0
                                                 - rParagraph.aBoundRect.Center().Y();
870
0
                            // vertical alignment adjust
871
0
                            fCurvesDist -= rTextArea.nHAlignMove;
872
0
873
0
                            for (i = 0; i < nPointCount; i++)
874
0
                            {
875
0
                                vCurOutline[i]
876
0
                                    = rOutlinePoly.GetPoint(i) + vNorm[i] * fCurvesDist / 1024.0;
877
0
                                if (i > 0)
878
0
                                {
879
0
                                    //calculate distances between points on the outer outline
880
0
                                    const double fDx = vCurOutline[i].X() - vCurOutline[i - 1].X();
881
0
                                    const double fDy = vCurOutline[i].Y() - vCurOutline[i - 1].Y();
882
0
                                    vCurDistances[i] = hypot(fDx, fDy);
883
0
                                }
884
0
                                else
885
0
                                    vCurDistances[i] = 0;
886
0
                            }
887
0
                            std::partial_sum(vCurDistances.begin(), vCurDistances.end(),
888
0
                                             vCurDistances.begin());
889
0
                            fCurWidth = vCurDistances[vCurDistances.size() - 1];
890
0
                            if (fCurWidth > 0.0)
891
0
                            {
892
0
                                for (auto& rDistance : vCurDistances)
893
0
                                    rDistance /= fCurWidth;
894
0
                            }
895
0
896
0
                            // if the current outline is longer than the text to fit in,
897
0
                            // then we have to divide the bonus space between the
898
0
                            // before-/after- text area.
899
0
                            // fAdjust means how much space we put before the text.
900
0
                            if (fCurWidth > rParagraph.aBoundRect.GetWidth())
901
0
                            {
902
0
                                fAdjust
903
0
                                    = nAdjust * (fCurWidth - rParagraph.aBoundRect.GetWidth()) / 2;
904
0
                            }
905
0
                            else
906
0
                                fAdjust = -1;   // we need to shrink the text to fit the curve
907
0
908
0
                            for ( auto& rCharacter : rParagraph.vCharacters )
909
0
                            {
910
0
                                for (tools::PolyPolygon& rPolyPoly : rCharacter.vOutlines)
911
0
                                {
912
0
                                    tools::Rectangle aBoundRect(rPolyPoly.GetBoundRect());
913
0
                                    double fx1 = aBoundRect.Left() - nLeft;
914
0
                                    double fx2 = aBoundRect.Right() - nLeft;
915
0
916
0
                                    double fParaRectWidth = rParagraph.aBoundRect.GetWidth();
917
0
                                    // Undo Horizontal alignment, hacked into poly coords,
918
0
                                    // so we can calculate it the right way
919
0
                                    double fHA = (rFWData.fHorizontalTextScaling
920
0
                                                      * rTextArea.aBoundRect.GetWidth()
921
0
                                                  - rParagraph.aBoundRect.GetWidth())
922
0
                                                 * nAdjust / 2;
923
0
924
0
                                    fx1 -= fHA;
925
0
                                    fx2 -= fHA;
926
0
927
0
                                    double fy1, fy2;
928
0
                                    double fM1 = fx1 / fParaRectWidth;
929
0
                                    double fM2 = fx2 / fParaRectWidth;
930
0
931
0
                                    // if fAdjust<0, then it means, the text was longer, as
932
0
                                    // the current outline, so we will skip the text scaling, and
933
0
                                    // the text horizontal alignment adjustment
934
0
                                    // so the text will be rendered just as long as the curve is.
935
0
                                    if (fAdjust >= 0)
936
0
                                    {
937
0
                                        fM1 = (fM1 * fParaRectWidth + fAdjust) / fCurWidth;
938
0
                                        fM2 = (fM2 * fParaRectWidth + fAdjust) / fCurWidth;
939
0
                                    }
940
0
                                    // 0 <= fM1,fM2 <= 1 should be true, but rounding errors can
941
0
                                    // make a small mistake.
942
0
                                    // make sure they are >0 because GetPoint() need that
943
0
                                    if (fM1 < 0) fM1 = 0;
944
0
                                    if (fM2 < 0) fM2 = 0;
945
0
946
0
                                    GetPoint(vCurOutline, vCurDistances, fM1, fx1, fy1);
947
0
                                    GetPoint(vCurOutline, vCurDistances, fM2, fx2, fy2);
948
0
949
0
                                    double fvx = fy2 - fy1;
950
0
                                    double fvy = - ( fx2 - fx1 );
951
0
                                    fx1 = fx1 + ( ( fx2 - fx1 ) * 0.5 );
952
0
                                    fy1 = fy1 + ( ( fy2 - fy1 ) * 0.5 );
953
0
954
0
                                    double fAngle = atan2( -fvx, -fvy );
955
0
                                    double fL = hypot( fvx, fvy );
956
0
                                    if (fL == 0.0)
957
0
                                    {
958
0
                                        SAL_WARN("svx", "FitTextOutlinesToShapeOutlines div-by-zero, abandon fit");
959
0
                                        break;
960
0
                                    }
961
0
                                    fvx = fvx / fL;
962
0
                                    fvy = fvy / fL;
963
0
                                    // Undo Vertical alignment hacked into poly coords
964
0
                                    // We already calculated the right alignment into the curve
965
0
                                    fL = rTextArea.nHAlignMove;
966
0
                                    fvx *= fL;
967
0
                                    fvy *= fL;
968
0
                                    rPolyPoly.Rotate( Point( aBoundRect.Center().X(), rParagraph.aBoundRect.Center().Y() ), sin( fAngle ), cos( fAngle ) );
969
0
                                    rPolyPoly.Move( static_cast<sal_Int32>( ( fx1 + fvx )- aBoundRect.Center().X() ), static_cast<sal_Int32>( ( fy1 + fvy ) - rParagraph.aBoundRect.Center().Y() ) );
970
0
                                }
971
0
                            }
972
0
                        }
973
0
                    }
974
0
                    else
975
0
                    {
976
0
                        // Fallback / old way to handle multiple lines:
977
0
                        // Every text lines use the same original outline (curve),
978
0
                        // it just scale character coordinates to fit to the right text line
979
0
                        // (curve), resulting wider/thinner space between characters
980
0
                        for (auto& rParagraph : rTextArea.vParagraphs)
981
0
                        {
982
0
                            for (auto& rCharacter : rParagraph.vCharacters)
983
0
                            {
984
0
                                for (tools::PolyPolygon& rPolyPoly : rCharacter.vOutlines)
985
0
                                {
986
0
                                    tools::Rectangle aBoundRect(rPolyPoly.GetBoundRect());
987
0
                                    double fx1 = aBoundRect.Left() - nLeft;
988
0
                                    double fx2 = aBoundRect.Right() - nLeft;
989
0
                                    double fy1, fy2;
990
0
                                    double fM1 = fx1 / static_cast<double>(nWidth);
991
0
                                    double fM2 = fx2 / static_cast<double>(nWidth);
992
0
993
0
                                    GetPoint(rOutlinePoly, vDistances, fM1, fx1, fy1);
994
0
                                    GetPoint(rOutlinePoly, vDistances, fM2, fx2, fy2);
995
0
996
0
                                    double fvx = fy2 - fy1;
997
0
                                    double fvy = -(fx2 - fx1);
998
0
                                    fx1 = fx1 + ((fx2 - fx1) * 0.5);
999
0
                                    fy1 = fy1 + ((fy2 - fy1) * 0.5);
1000
0
1001
0
                                    double fAngle = atan2(-fvx, -fvy);
1002
0
                                    double fL = hypot(fvx, fvy);
1003
0
                                    if (fL == 0.0)
1004
0
                                    {
1005
0
                                        SAL_WARN("svx", "FitTextOutlinesToShapeOutlines div-by-zero, abandon fit");
1006
0
                                        break;
1007
0
                                    }
1008
0
                                    fvx = fvx / fL;
1009
0
                                    fvy = fvy / fL;
1010
0
                                    fL = rTextArea.aBoundRect.GetHeight() / 2.0 + rTextArea.aBoundRect.Top() - rParagraph.aBoundRect.Center().Y();
1011
0
                                    fvx *= fL;
1012
0
                                    fvy *= fL;
1013
0
                                    rPolyPoly.Rotate( Point( aBoundRect.Center().X(), rParagraph.aBoundRect.Center().Y() ), sin( fAngle ), cos( fAngle ) );
1014
0
                                    rPolyPoly.Move( static_cast<sal_Int32>( ( fx1 + fvx )- aBoundRect.Center().X() ), static_cast<sal_Int32>( ( fy1 + fvy ) - rParagraph.aBoundRect.Center().Y() ) );
1015
0
                                }
1016
0
                            }
1017
0
                        }
1018
0
                    }
1019
0
1020
0
                }
1021
0
            }
1022
0
        }
1023
0
        else
1024
0
        {
1025
0
            if ( ( nOutline2dIdx + 1 ) >= aOutlines2d.Count() )
1026
0
                break;
1027
0
            const tools::Polygon& rOutlinePoly( aOutlines2d[ nOutline2dIdx++ ] );
1028
0
            const tools::Polygon& rOutlinePoly2( aOutlines2d[ nOutline2dIdx++ ] );
1029
0
            const sal_uInt16 nPointCount = rOutlinePoly.GetSize();
1030
0
            const sal_uInt16 nPointCount2 = rOutlinePoly2.GetSize();
1031
0
            if ( ( nPointCount > 1 ) && ( nPointCount2 > 1 ) )
1032
0
            {
1033
0
                std::vector< double > vDistances;
1034
0
                vDistances.reserve( nPointCount );
1035
0
                std::vector< double > vDistances2;
1036
0
                vDistances2.reserve( nPointCount2 );
1037
0
                CalcDistances( rOutlinePoly, vDistances );
1038
0
                CalcDistances( rOutlinePoly2, vDistances2 );
1039
0
                for( auto& rParagraph : rTextArea.vParagraphs )
1040
0
                {
1041
0
                    for ( auto& rCharacter : rParagraph.vCharacters )
1042
0
                    {
1043
0
                        for( tools::PolyPolygon& rPolyPoly : rCharacter.vOutlines )
1044
0
                        {
1045
0
                            sal_uInt16 i, nPolyCount = rPolyPoly.Count();
1046
0
                            for ( i = 0; i < nPolyCount; i++ )
1047
0
                            {
1048
0
                                // #i35928#
1049
0
                                basegfx::B2DPolygon aCandidate(rPolyPoly[ i ].getB2DPolygon());
1050
0
1051
0
                                if(aCandidate.areControlPointsUsed())
1052
0
                                {
1053
0
                                    aCandidate = basegfx::utils::adaptiveSubdivideByAngle(aCandidate);
1054
0
                                }
1055
0
1056
0
                                // create local polygon copy to work on
1057
0
                                tools::Polygon aLocalPoly(aCandidate);
1058
0
1059
0
                                InsertMissingOutlinePoints( vDistances, rTextAreaBoundRect, aLocalPoly );
1060
0
                                InsertMissingOutlinePoints( vDistances2, rTextAreaBoundRect, aLocalPoly );
1061
0
1062
0
                                sal_uInt16 _nPointCount = aLocalPoly.GetSize();
1063
0
                                if (_nPointCount)
1064
0
                                {
1065
0
                                    if (!nWidth || !nHeight)
1066
0
                                        throw o3tl::divide_by_zero();
1067
0
                                    for (sal_uInt16 j = 0; j < _nPointCount; ++j)
1068
0
                                    {
1069
0
                                        Point& rPoint = aLocalPoly[ j ];
1070
0
                                        rPoint.AdjustX( -nLeft );
1071
0
                                        rPoint.AdjustY( -nTop );
1072
0
                                        double fX = static_cast<double>(rPoint.X()) / static_cast<double>(nWidth);
1073
0
                                        double fY = static_cast<double>(rPoint.Y()) / static_cast<double>(nHeight);
1074
0
1075
0
                                        double fx1, fy1, fx2, fy2;
1076
0
                                        GetPoint( rOutlinePoly, vDistances, fX, fx1, fy1 );
1077
0
                                        GetPoint( rOutlinePoly2, vDistances2, fX, fx2, fy2 );
1078
0
                                        double fWidth = fx2 - fx1;
1079
0
                                        double fHeight= fy2 - fy1;
1080
0
                                        rPoint.setX( static_cast<sal_Int32>( fx1 + fWidth * fY ) );
1081
0
                                        rPoint.setY( static_cast<sal_Int32>( fy1 + fHeight* fY ) );
1082
0
                                    }
1083
0
                                }
1084
0
1085
0
                                // write back polygon
1086
0
                                rPolyPoly[i] = std::move(aLocalPoly);
1087
0
                            }
1088
0
                        }
1089
0
                    }
1090
0
                }
1091
0
            }
1092
0
        }
1093
0
    }
1094
0
}
1095
1096
static rtl::Reference<SdrObject> CreateSdrObjectFromParagraphOutlines(
1097
    const FWData& rFWData,
1098
    const SdrObjCustomShape& rSdrObjCustomShape)
1099
0
{
1100
0
    rtl::Reference<SdrObject> pRet;
1101
0
    basegfx::B2DPolyPolygon aPolyPoly;
1102
0
    if ( !rFWData.vTextAreas.empty() )
1103
0
    {
1104
0
        for ( const auto& rTextArea : rFWData.vTextAreas )
1105
0
        {
1106
0
            for ( const auto& rParagraph : rTextArea.vParagraphs )
1107
0
            {
1108
0
                for ( const auto& rCharacter : rParagraph.vCharacters )
1109
0
                {
1110
0
                    for( const auto& rOutline : rCharacter.vOutlines )
1111
0
                    {
1112
0
                        aPolyPoly.append( rOutline.getB2DPolyPolygon() );
1113
0
                    }
1114
0
                }
1115
0
            }
1116
0
        }
1117
0
1118
0
        pRet = new SdrPathObj(
1119
0
            rSdrObjCustomShape.getSdrModelFromSdrObject(),
1120
0
            SdrObjKind::Polygon,
1121
0
            std::move(aPolyPoly));
1122
0
1123
0
        SfxItemSet aSet(rSdrObjCustomShape.GetMergedItemSet());
1124
0
        aSet.ClearItem( SDRATTR_TEXTDIRECTION );    //SJ: vertical writing is not required, by removing this item no outliner is created
1125
0
        aSet.Put(makeSdrShadowItem(false)); // #i37011# NO shadow for FontWork geometry
1126
0
        pRet->SetMergedItemSet( aSet );             // * otherwise we would crash, because the outliner tries to create a Paraobject, but there is no model
1127
0
    }
1128
0
1129
0
    return pRet;
1130
0
}
1131
1132
Reference < i18n::XBreakIterator > EnhancedCustomShapeFontWork::mxBreakIterator;
1133
1134
Reference < i18n::XBreakIterator > const & EnhancedCustomShapeFontWork::GetBreakIterator()
1135
0
{
1136
0
    if ( !mxBreakIterator.is() )
1137
0
    {
1138
0
        const Reference< uno::XComponentContext >& xContext = ::comphelper::getProcessComponentContext();
1139
0
        mxBreakIterator = i18n::BreakIterator::create(xContext);
1140
0
    }
1141
0
    return mxBreakIterator;
1142
0
}
1143
1144
rtl::Reference<SdrObject> EnhancedCustomShapeFontWork::CreateFontWork(
1145
    const SdrObject* pShape2d,
1146
    const SdrObjCustomShape& rSdrObjCustomShape)
1147
2.88k
{
1148
2.88k
    rtl::Reference<SdrObject> pRet;
1149
1150
    // calculating scaling factor is too slow
1151
2.88k
    if (comphelper::IsFuzzing())
1152
2.88k
        return pRet;
1153
1154
0
    tools::PolyPolygon aOutlines2d( GetOutlinesFromShape2d( pShape2d ) );
1155
0
    sal_uInt16 nOutlinesCount2d = aOutlines2d.Count();
1156
0
    if ( nOutlinesCount2d )
1157
0
    {
1158
0
        FWData aFWData;
1159
1160
0
        if(InitializeFontWorkData(rSdrObjCustomShape, nOutlinesCount2d, aFWData))
1161
0
        {
1162
            /* retrieves the horizontal scaling factor that has to be used
1163
            to fit each paragraph text into its corresponding 2d outline */
1164
0
            CalculateHorizontalScalingFactor(
1165
0
                rSdrObjCustomShape,
1166
0
                aFWData,
1167
0
                aOutlines2d);
1168
1169
            /* retrieving the Outlines for the each Paragraph. */
1170
0
            if(!GetFontWorkOutline(
1171
0
                aFWData,
1172
0
                rSdrObjCustomShape))
1173
0
            {
1174
0
                return nullptr;
1175
0
            }
1176
1177
0
            SdrTextHorzAdjust eHorzAdjust(
1178
0
                rSdrObjCustomShape.GetMergedItem(SDRATTR_TEXT_HORZADJUST).GetValue());
1179
0
            bool bPPFontwork = !rSdrObjCustomShape.getSdrModelFromSdrObject().GetCompatibilityFlag(
1180
0
                              SdrCompatibilityFlag::LegacyFontwork);
1181
0
            FitTextOutlinesToShapeOutlines( aOutlines2d, aFWData, eHorzAdjust, bPPFontwork );
1182
1183
0
            pRet = CreateSdrObjectFromParagraphOutlines(
1184
0
                aFWData,
1185
0
                rSdrObjCustomShape);
1186
0
        }
1187
0
    }
1188
0
    return pRet;
1189
0
}
1190
1191
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */