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/svddef.hxx>
23 #include <svx/svdopath.hxx>
24 #include <vcl/metric.hxx>
25 #include <svx/sdasitm.hxx>
26 #include <svx/sdtfsitm.hxx>
27 #include <vcl/virdev.hxx>
28 #include <svx/svditer.hxx>
29 #include <editeng/eeitem.hxx>
30 #include <editeng/frmdiritem.hxx>
31 #include <editeng/fontitem.hxx>
32 #include <editeng/postitem.hxx>
33 #include <editeng/wghtitem.hxx>
34 #include <editeng/fhgtitem.hxx>
35 #include <editeng/charscaleitem.hxx>
36 #include <svx/svdoashp.hxx>
37 #include <svx/sdshitm.hxx>
38 #include <editeng/outlobj.hxx>
39 #include <editeng/editobj.hxx>
40 #include <o3tl/numeric.hxx>
41 #include <vector>
42 #include <numeric>
43 #include <algorithm>
44 #include <memory>
45 #include <comphelper/processfactory.hxx>
46 #include <com/sun/star/i18n/BreakIterator.hpp>
47 #include <com/sun/star/i18n/ScriptType.hpp>
48 #include <basegfx/polygon/b2dpolypolygontools.hxx>
49 #include <basegfx/polygon/b2dpolygontools.hxx>
50 #include <sal/log.hxx>
51 #include <rtl/math.hxx>
52 
53 using namespace com::sun::star;
54 using namespace com::sun::star::uno;
55 
56 namespace {
57 
58 struct FWCharacterData                  // representing a single character
59 {
60     std::vector< tools::PolyPolygon >   vOutlines;
61     tools::Rectangle                           aBoundRect;
62 };
63 struct FWParagraphData                  // representing a single paragraph
64 {
65     OUString                            aString;
66     std::vector< FWCharacterData >      vCharacters;
67     tools::Rectangle                           aBoundRect;
68     SvxFrameDirection                   nFrameDirection;
69 };
70 struct FWTextArea                       // representing multiple concluding paragraphs
71 {
72     std::vector< FWParagraphData >      vParagraphs;
73     tools::Rectangle                           aBoundRect;
74 };
75 struct FWData                           // representing the whole text
76 {
77     std::vector< FWTextArea >           vTextAreas;
78     double                              fHorizontalTextScaling;
79     double                              fVerticalTextScaling;
80     sal_uInt32                          nMaxParagraphsPerTextArea;
81     sal_Int32                           nSingleLineHeight;
82     bool                                bSingleLineMode;
83     bool                                bScaleX;
84 };
85 
86 }
87 
88 static bool InitializeFontWorkData(
89     const SdrObjCustomShape& rSdrObjCustomShape,
90     const sal_uInt16 nOutlinesCount2d,
91     FWData& rFWData)
92 {
93     bool bNoErr = false;
94     bool bSingleLineMode = false;
95     sal_uInt16 nTextAreaCount = nOutlinesCount2d;
96     if ( nOutlinesCount2d & 1 )
97         bSingleLineMode = true;
98     else
99         nTextAreaCount >>= 1;
100 
101     const SdrCustomShapeGeometryItem& rGeometryItem( rSdrObjCustomShape.GetMergedItem( SDRATTR_CUSTOMSHAPE_GEOMETRY ) );
102     const css::uno::Any* pAny = rGeometryItem.GetPropertyValueByName( "TextPath", "ScaleX" );
103     if (pAny)
104         *pAny >>= rFWData.bScaleX;
105     else
106         rFWData.bScaleX = false;
107 
108     if ( nTextAreaCount )
109     {
110         rFWData.bSingleLineMode = bSingleLineMode;
111 
112         // setting the strings
113         OutlinerParaObject* pParaObj(rSdrObjCustomShape.GetOutlinerParaObject());
114 
115         if ( pParaObj )
116         {
117             const EditTextObject& rTextObj = pParaObj->GetTextObject();
118             sal_Int32 nParagraphsLeft = rTextObj.GetParagraphCount();
119 
120             rFWData.nMaxParagraphsPerTextArea = ( ( nParagraphsLeft - 1 ) / nTextAreaCount ) + 1;
121             sal_Int32 j = 0;
122             while( nParagraphsLeft && nTextAreaCount )
123             {
124                 FWTextArea aTextArea;
125                 sal_Int32 i, nParagraphs = ( ( nParagraphsLeft - 1 ) / nTextAreaCount ) + 1;
126                 for ( i = 0; i < nParagraphs; ++i, ++j )
127                 {
128                     FWParagraphData aParagraphData;
129                     aParagraphData.aString = rTextObj.GetText( j );
130 
131                     const SfxItemSet& rParaSet = rTextObj.GetParaAttribs( j );  // retrieving some paragraph attributes
132                     aParagraphData.nFrameDirection = rParaSet.Get( EE_PARA_WRITINGDIR ).GetValue();
133                     aTextArea.vParagraphs.push_back( aParagraphData );
134                 }
135                 rFWData.vTextAreas.push_back( aTextArea );
136                 nParagraphsLeft -= nParagraphs;
137                 nTextAreaCount--;
138             }
139             bNoErr = true;
140         }
141     }
142     return bNoErr;
143 }
144 
145 static double GetLength( const tools::Polygon& rPolygon )
146 {
147     double fLength = 0;
148     if ( rPolygon.GetSize() > 1 )
149     {
150         sal_uInt16 nCount = rPolygon.GetSize();
151         while( --nCount )
152             fLength += rPolygon.CalcDistance( nCount, nCount - 1 );
153     }
154     return fLength;
155 }
156 
157 
158 /* CalculateHorizontalScalingFactor returns the horizontal scaling factor for
159 the whole text object, so that each text will match its corresponding 2d Outline */
160 static void CalculateHorizontalScalingFactor(
161     const SdrObjCustomShape& rSdrObjCustomShape,
162     FWData& rFWData,
163     const tools::PolyPolygon& rOutline2d)
164 {
165     double fScalingFactor = 1.0;
166     bool bScalingFactorDefined = false;
167     rFWData.fVerticalTextScaling = 1.0;
168 
169     sal_uInt16 i = 0;
170     bool bSingleLineMode = false;
171     sal_uInt16 nOutlinesCount2d = rOutline2d.Count();
172 
173     vcl::Font aFont;
174     const SvxFontItem& rFontItem( rSdrObjCustomShape.GetMergedItem( EE_CHAR_FONTINFO ) );
175     const SvxFontHeightItem& rFontHeight( rSdrObjCustomShape.GetMergedItem( EE_CHAR_FONTHEIGHT ) );
176     sal_Int32 nFontSize = rFontHeight.GetHeight();
177 
178     if (rFWData.bScaleX)
179         aFont.SetFontHeight( nFontSize );
180     else
181         aFont.SetFontHeight( rSdrObjCustomShape.GetLogicRect().GetHeight() / rFWData.nMaxParagraphsPerTextArea );
182 
183     aFont.SetAlignment( ALIGN_TOP );
184     aFont.SetFamilyName( rFontItem.GetFamilyName() );
185     aFont.SetFamily( rFontItem.GetFamily() );
186     aFont.SetStyleName( rFontItem.GetStyleName() );
187     const SvxPostureItem& rPostureItem = rSdrObjCustomShape.GetMergedItem( EE_CHAR_ITALIC );
188     aFont.SetItalic( rPostureItem.GetPosture() );
189 
190     const SvxWeightItem& rWeightItem = rSdrObjCustomShape.GetMergedItem( EE_CHAR_WEIGHT );
191     aFont.SetWeight( rWeightItem.GetWeight() );
192     aFont.SetOrientation( 0 );
193     // initializing virtual device
194 
195     ScopedVclPtrInstance< VirtualDevice > pVirDev(DeviceFormat::BITMASK);
196     pVirDev->SetMapMode(MapMode(MapUnit::Map100thMM));
197     pVirDev->SetFont( aFont );
198 
199     if ( nOutlinesCount2d & 1 )
200         bSingleLineMode = true;
201 
202     do
203     {
204         i = 0;
205         for( const auto& rTextArea : rFWData.vTextAreas )
206         {
207             // calculating the width of the corresponding 2d text area
208             double fWidth = GetLength( rOutline2d.GetObject( i++ ) );
209             if ( !bSingleLineMode )
210             {
211                 fWidth += GetLength( rOutline2d.GetObject( i++ ) );
212                 fWidth /= 2.0;
213             }
214 
215             for( const auto& rParagraph : rTextArea.vParagraphs )
216             {
217                 double fTextWidth = pVirDev->GetTextWidth( rParagraph.aString );
218                 if ( fTextWidth > 0.0 )
219                 {
220                     double fScale = fWidth / fTextWidth;
221                     if ( !bScalingFactorDefined )
222                     {
223                         fScalingFactor = fScale;
224                         bScalingFactorDefined = true;
225                     }
226                     else if ( fScale < fScalingFactor || ( rFWData.bScaleX && fScalingFactor < 1.0 ) )
227                     {
228                         fScalingFactor = fScale;
229                     }
230                 }
231             }
232         }
233 
234         if (fScalingFactor < 1.0)
235         {
236             nFontSize--;
237             aFont.SetFontHeight( nFontSize );
238             pVirDev->SetFont( aFont );
239         }
240     }
241     while (rFWData.bScaleX && fScalingFactor < 1.0 && nFontSize > 1 );
242 
243     if (nFontSize > 1)
244         rFWData.fVerticalTextScaling = static_cast<double>(nFontSize) / rFontHeight.GetHeight();
245     // Add some padding
246     if (rFWData.bScaleX)
247         fScalingFactor *= 1.1;
248 
249     rFWData.fHorizontalTextScaling = fScalingFactor;
250 }
251 
252 static void GetTextAreaOutline(
253     const FWData& rFWData,
254     const SdrObjCustomShape& rSdrObjCustomShape,
255     FWTextArea& rTextArea,
256     bool bSameLetterHeights)
257 {
258     bool bIsVertical(rSdrObjCustomShape.IsVerticalWriting());
259     sal_Int32 nVerticalOffset = rFWData.nMaxParagraphsPerTextArea > rTextArea.vParagraphs.size()
260                                     ? rFWData.nSingleLineHeight / 2 : 0;
261 
262     for( auto& rParagraph : rTextArea.vParagraphs )
263     {
264         const OUString& rText = rParagraph.aString;
265         if ( !rText.isEmpty() )
266         {
267             // generating vcl/font
268             sal_uInt16 nScriptType = i18n::ScriptType::LATIN;
269             Reference< i18n::XBreakIterator > xBI( EnhancedCustomShapeFontWork::GetBreakIterator() );
270             if ( xBI.is() )
271             {
272                 nScriptType = xBI->getScriptType( rText, 0 );
273                 if( i18n::ScriptType::WEAK == nScriptType )
274                 {
275                     sal_Int32 nChg = xBI->endOfScript( rText, 0, nScriptType );
276                     if (nChg < rText.getLength() && nChg >= 0)
277                         nScriptType = xBI->getScriptType( rText, nChg );
278                     else
279                         nScriptType = i18n::ScriptType::LATIN;
280                 }
281             }
282             sal_uInt16 nFntItm = EE_CHAR_FONTINFO;
283             if ( nScriptType == i18n::ScriptType::COMPLEX )
284                 nFntItm = EE_CHAR_FONTINFO_CTL;
285             else if ( nScriptType == i18n::ScriptType::ASIAN )
286                 nFntItm = EE_CHAR_FONTINFO_CJK;
287             const SvxFontItem& rFontItem = static_cast<const SvxFontItem&>(rSdrObjCustomShape.GetMergedItem( nFntItm ));
288             vcl::Font aFont;
289 
290             aFont.SetFontHeight( rFWData.nSingleLineHeight );
291 
292             aFont.SetAlignment( ALIGN_TOP );
293 
294             aFont.SetFamilyName( rFontItem.GetFamilyName() );
295             aFont.SetFamily( rFontItem.GetFamily() );
296             aFont.SetStyleName( rFontItem.GetStyleName() );
297             aFont.SetOrientation( 0 );
298 
299             const SvxPostureItem& rPostureItem = rSdrObjCustomShape.GetMergedItem( EE_CHAR_ITALIC );
300             aFont.SetItalic( rPostureItem.GetPosture() );
301 
302             const SvxWeightItem& rWeightItem = rSdrObjCustomShape.GetMergedItem( EE_CHAR_WEIGHT );
303             aFont.SetWeight( rWeightItem.GetWeight() );
304 
305             // initializing virtual device
306             ScopedVclPtrInstance< VirtualDevice > pVirDev(DeviceFormat::BITMASK);
307             pVirDev->SetMapMode(MapMode(MapUnit::Map100thMM));
308             pVirDev->SetFont( aFont );
309             pVirDev->EnableRTL();
310             if ( rParagraph.nFrameDirection == SvxFrameDirection::Horizontal_RL_TB )
311                 pVirDev->SetLayoutMode( ComplexTextLayoutFlags::BiDiRtl );
312 
313             const SvxCharScaleWidthItem& rCharScaleWidthItem = rSdrObjCustomShape.GetMergedItem( EE_CHAR_FONTWIDTH );
314             sal_uInt16 nCharScaleWidth = rCharScaleWidthItem.GetValue();
315             std::unique_ptr<long[]> pDXArry;
316             sal_Int32 nWidth = 0;
317 
318             // VERTICAL
319             if ( bIsVertical )
320             {
321                 // vertical _> each single character needs to be rotated by 90
322                 sal_Int32 i;
323                 sal_Int32 nHeight = 0;
324                 tools::Rectangle aSingleCharacterUnion;
325                 for ( i = 0; i < rText.getLength(); i++ )
326                 {
327                     FWCharacterData aCharacterData;
328                     OUString aCharText( rText[ i ] );
329                     if ( pVirDev->GetTextOutlines( aCharacterData.vOutlines, aCharText, 0, 0, -1, nWidth, pDXArry.get() ) )
330                     {
331                         sal_Int32 nTextWidth = pVirDev->GetTextWidth( aCharText);
332                         if ( aCharacterData.vOutlines.empty() )
333                         {
334                             nHeight += rFWData.nSingleLineHeight;
335                         }
336                         else
337                         {
338                             for ( auto& rOutline : aCharacterData.vOutlines )
339                             {
340                                 // rotating
341                                 rOutline.Rotate( Point( nTextWidth / 2, rFWData.nSingleLineHeight / 2 ), 900 );
342                                 aCharacterData.aBoundRect.Union( rOutline.GetBoundRect() );
343                             }
344                             for ( auto& rOutline : aCharacterData.vOutlines )
345                             {
346                                 sal_Int32 nM = - aCharacterData.aBoundRect.Left() + nHeight;
347                                 rOutline.Move( nM, 0 );
348                                 aCharacterData.aBoundRect.Move( nM, 0 );
349                             }
350                             nHeight += aCharacterData.aBoundRect.GetWidth() + ( rFWData.nSingleLineHeight / 5 );
351                             aSingleCharacterUnion.Union( aCharacterData.aBoundRect );
352                         }
353                     }
354                     rParagraph.vCharacters.push_back( aCharacterData );
355                 }
356                 for ( auto& rCharacter : rParagraph.vCharacters )
357                 {
358                     for ( auto& rOutline : rCharacter.vOutlines )
359                     {
360                         rOutline.Move( ( aSingleCharacterUnion.GetWidth() - rCharacter.aBoundRect.GetWidth() ) / 2, 0 );
361                     }
362                 }
363             }
364             else
365             {
366                 if ( ( nCharScaleWidth != 100 ) && nCharScaleWidth )
367                 {   // applying character spacing
368                     pDXArry.reset(new long[ rText.getLength() ]);
369                     pVirDev->GetTextArray( rText, pDXArry.get());
370                     FontMetric aFontMetric( pVirDev->GetFontMetric() );
371                     aFont.SetAverageFontWidth( static_cast<sal_Int32>( static_cast<double>(aFontMetric.GetAverageFontWidth()) * ( double(100) / static_cast<double>(nCharScaleWidth) ) ) );
372                     pVirDev->SetFont( aFont );
373                 }
374                 FWCharacterData aCharacterData;
375                 if ( pVirDev->GetTextOutlines( aCharacterData.vOutlines, rText, 0, 0, -1, nWidth, pDXArry.get() ) )
376                 {
377                     rParagraph.vCharacters.push_back( aCharacterData );
378                 }
379             }
380 
381             // vertical alignment
382             for ( auto& rCharacter : rParagraph.vCharacters )
383             {
384                 for( tools::PolyPolygon& rPolyPoly : rCharacter.vOutlines )
385                 {
386                     if ( nVerticalOffset )
387                         rPolyPoly.Move( 0, nVerticalOffset );
388 
389                     // retrieving the boundrect for the paragraph
390                     tools::Rectangle aBoundRect( rPolyPoly.GetBoundRect() );
391                     rParagraph.aBoundRect.Union( aBoundRect );
392                 }
393             }
394         }
395         // updating the boundrect for the text area by merging the current paragraph boundrect
396         if ( rParagraph.aBoundRect.IsEmpty() )
397         {
398             if ( rTextArea.aBoundRect.IsEmpty() )
399                 rTextArea.aBoundRect = tools::Rectangle( Point( 0, 0 ), Size( 1, rFWData.nSingleLineHeight ) );
400             else
401                 rTextArea.aBoundRect.AdjustBottom(rFWData.nSingleLineHeight );
402         }
403         else
404         {
405             tools::Rectangle& rParagraphBoundRect = rParagraph.aBoundRect;
406             rTextArea.aBoundRect.Union( rParagraphBoundRect );
407 
408             if ( bSameLetterHeights )
409             {
410                 for ( auto& rCharacter : rParagraph.vCharacters )
411                 {
412                     for( auto& rOutline : rCharacter.vOutlines )
413                     {
414                         tools::Rectangle aPolyPolyBoundRect( rOutline.GetBoundRect() );
415                         if (aPolyPolyBoundRect.GetHeight() != rParagraphBoundRect.GetHeight() && aPolyPolyBoundRect.GetHeight())
416                             rOutline.Scale( 1.0, static_cast<double>(rParagraphBoundRect.GetHeight()) / aPolyPolyBoundRect.GetHeight() );
417                         aPolyPolyBoundRect = rOutline.GetBoundRect();
418                         sal_Int32 nMove = aPolyPolyBoundRect.Top() - rParagraphBoundRect.Top();
419                         if ( nMove )
420                             rOutline.Move( 0, -nMove );
421                     }
422                 }
423             }
424         }
425         if ( bIsVertical )
426             nVerticalOffset -= rFWData.nSingleLineHeight;
427         else
428             nVerticalOffset += rFWData.nSingleLineHeight;
429     }
430 }
431 
432 static bool GetFontWorkOutline(
433     FWData& rFWData,
434     const SdrObjCustomShape& rSdrObjCustomShape)
435 {
436     SdrTextHorzAdjust eHorzAdjust(rSdrObjCustomShape.GetMergedItem( SDRATTR_TEXT_HORZADJUST ).GetValue());
437     drawing::TextFitToSizeType const eFTS(rSdrObjCustomShape.GetMergedItem( SDRATTR_TEXT_FITTOSIZE ).GetValue());
438 
439     bool bSameLetterHeights = false;
440     const SdrCustomShapeGeometryItem& rGeometryItem(rSdrObjCustomShape.GetMergedItem( SDRATTR_CUSTOMSHAPE_GEOMETRY ));
441     const css::uno::Any* pAny = rGeometryItem.GetPropertyValueByName( "TextPath", "SameLetterHeights" );
442     if ( pAny )
443         *pAny >>= bSameLetterHeights;
444 
445     const SvxFontHeightItem& rFontHeight( rSdrObjCustomShape.GetMergedItem( EE_CHAR_FONTHEIGHT ) );
446     if (rFWData.bScaleX)
447         rFWData.nSingleLineHeight = rFWData.fVerticalTextScaling * rFontHeight.GetHeight();
448     else
449         rFWData.nSingleLineHeight = static_cast<sal_Int32>( ( static_cast<double>( rSdrObjCustomShape.GetLogicRect().GetHeight() )
450                                                     / rFWData.nMaxParagraphsPerTextArea ) * rFWData.fHorizontalTextScaling );
451 
452     if (rFWData.nSingleLineHeight == SAL_MIN_INT32)
453         return false;
454 
455     for ( auto& rTextArea : rFWData.vTextAreas )
456     {
457         GetTextAreaOutline(
458             rFWData,
459             rSdrObjCustomShape,
460             rTextArea,
461             bSameLetterHeights);
462 
463         if (eFTS == drawing::TextFitToSizeType_ALLLINES ||
464             // tdf#97630 interpret PROPORTIONAL same as ALLLINES so we don't
465             // need another ODF attribute!
466             eFTS == drawing::TextFitToSizeType_PROPORTIONAL)
467         {
468             for ( auto& rParagraph : rTextArea.vParagraphs )
469             {
470                 sal_Int32 nParaWidth = rParagraph.aBoundRect.GetWidth();
471                 if ( nParaWidth )
472                 {
473                     double fScale = static_cast<double>(rTextArea.aBoundRect.GetWidth()) / nParaWidth;
474 
475                     for ( auto& rCharacter : rParagraph.vCharacters )
476                     {
477                         for( auto& rOutline : rCharacter.vOutlines )
478                         {
479                             rOutline.Scale( fScale, 1.0 );
480                         }
481                     }
482                 }
483             }
484         }
485         else if (rFWData.bScaleX)
486         {
487             const SdrTextVertAdjust nVertJustify = rSdrObjCustomShape.GetMergedItem( SDRATTR_TEXT_VERTADJUST ).GetValue();
488             double fFactor = nVertJustify == SdrTextVertAdjust::SDRTEXTVERTADJUST_BOTTOM ? -0.5 : ( nVertJustify == SdrTextVertAdjust::SDRTEXTVERTADJUST_TOP ? 0.5 : 0 );
489 
490             for ( auto& rParagraph : rTextArea.vParagraphs )
491             {
492                 sal_Int32 nHorzDiff = 0;
493                 sal_Int32 nVertDiff = static_cast<double>( rFWData.nSingleLineHeight ) * fFactor * ( rTextArea.vParagraphs.size() - 1 );
494 
495                 if ( eHorzAdjust == SDRTEXTHORZADJUST_CENTER )
496                     nHorzDiff = ( rFWData.fHorizontalTextScaling * rTextArea.aBoundRect.GetWidth() - rParagraph.aBoundRect.GetWidth() ) / 2;
497                 else if ( eHorzAdjust == SDRTEXTHORZADJUST_RIGHT )
498                     nHorzDiff = ( rFWData.fHorizontalTextScaling * rTextArea.aBoundRect.GetWidth() - rParagraph.aBoundRect.GetWidth() );
499 
500                 if (nHorzDiff)
501                 {
502                     for ( auto& rCharacter : rParagraph.vCharacters )
503                     {
504                         for( auto& rOutline : rCharacter.vOutlines )
505                         {
506                             rOutline.Move( nHorzDiff, nVertDiff );
507                         }
508                     }
509                 }
510             }
511         }
512         else
513         {
514             switch( eHorzAdjust )
515             {
516                 case SDRTEXTHORZADJUST_RIGHT :
517                 case SDRTEXTHORZADJUST_CENTER:
518                 {
519                     for ( auto& rParagraph : rTextArea.vParagraphs )
520                     {
521                         sal_Int32 nHorzDiff = 0;
522                         if ( eHorzAdjust == SDRTEXTHORZADJUST_CENTER )
523                             nHorzDiff = ( rTextArea.aBoundRect.GetWidth() - rParagraph.aBoundRect.GetWidth() ) / 2;
524                         else if ( eHorzAdjust == SDRTEXTHORZADJUST_RIGHT )
525                             nHorzDiff = ( rTextArea.aBoundRect.GetWidth() - rParagraph.aBoundRect.GetWidth() );
526                         if ( nHorzDiff )
527                         {
528                             for ( auto& rCharacter : rParagraph.vCharacters )
529                             {
530                                 for( auto& rOutline : rCharacter.vOutlines )
531                                 {
532                                     rOutline.Move( nHorzDiff, 0 );
533                                 }
534                             }
535                         }
536                     }
537                 }
538                 break;
539                 default:
540                 case SDRTEXTHORZADJUST_BLOCK : break;   // don't know
541                 case SDRTEXTHORZADJUST_LEFT : break;    // already left aligned -> nothing to do
542             }
543         }
544     }
545 
546     return true;
547 }
548 
549 static basegfx::B2DPolyPolygon GetOutlinesFromShape2d( const SdrObject* pShape2d )
550 {
551     basegfx::B2DPolyPolygon aOutlines2d;
552 
553     SdrObjListIter aObjListIter( *pShape2d, SdrIterMode::DeepWithGroups );
554     while( aObjListIter.IsMore() )
555     {
556         SdrObject* pPartObj = aObjListIter.Next();
557         if ( dynamic_cast<const SdrPathObj*>( pPartObj) !=  nullptr )
558         {
559             basegfx::B2DPolyPolygon aCandidate(static_cast<SdrPathObj*>(pPartObj)->GetPathPoly());
560             if(aCandidate.areControlPointsUsed())
561             {
562                 aCandidate = basegfx::utils::adaptiveSubdivideByAngle(aCandidate);
563             }
564             aOutlines2d.append(aCandidate);
565         }
566     }
567 
568     return aOutlines2d;
569 }
570 
571 static void CalcDistances( const tools::Polygon& rPoly, std::vector< double >& rDistances )
572 {
573     sal_uInt16 i, nCount = rPoly.GetSize();
574     if ( nCount <= 1 )
575         return;
576 
577     for ( i = 0; i < nCount; i++ )
578     {
579         double fDistance = i ? rPoly.CalcDistance( i, i - 1 ) : 0.0;
580         rDistances.push_back( fDistance );
581     }
582     std::partial_sum( rDistances.begin(), rDistances.end(), rDistances.begin() );
583     double fLength = rDistances[ rDistances.size() - 1 ];
584     if ( fLength > 0.0 )
585     {
586         for ( auto& rDistance : rDistances )
587             rDistance /= fLength;
588     }
589 }
590 
591 static void InsertMissingOutlinePoints( const std::vector< double >& rDistances,
592                                  const tools::Rectangle& rTextAreaBoundRect, tools::Polygon& rPoly )
593 {
594     sal_uInt16 nSize = rPoly.GetSize();
595     if (nSize == 0)
596         return;
597 
598     long nTextWidth = rTextAreaBoundRect.GetWidth();
599 
600     if (nTextWidth == 0)
601         throw o3tl::divide_by_zero();
602 
603     double fLastDistance = 0.0;
604     for (sal_uInt16 i = 0; i < nSize; ++i)
605     {
606         Point& rPoint = rPoly[ i ];
607         double fDistance = static_cast<double>( rPoint.X() - rTextAreaBoundRect.Left() ) / static_cast<double>(nTextWidth);
608         if ( i )
609         {
610             if ( fDistance > fLastDistance )
611             {
612                 std::vector< double >::const_iterator aIter = std::upper_bound( rDistances.begin(), rDistances.end(), fLastDistance );
613                 if  ( aIter != rDistances.end() && ( *aIter > fLastDistance ) && ( *aIter < fDistance ) )
614                 {
615                     Point& rPt0 = rPoly[ i - 1 ];
616                     sal_Int32 fX = rPoint.X() - rPt0.X();
617                     sal_Int32 fY = rPoint.Y() - rPt0.Y();
618                     double fd = ( 1.0 / ( fDistance - fLastDistance ) ) * ( *aIter - fLastDistance );
619                     rPoly.Insert( i, Point( static_cast<sal_Int32>( rPt0.X() + fX * fd ), static_cast<sal_Int32>( rPt0.Y() + fY * fd ) ) );
620                     fDistance = *aIter;
621                 }
622             }
623             else if ( fDistance < fLastDistance )
624             {
625                 std::vector< double >::const_iterator aIter = std::lower_bound( rDistances.begin(), rDistances.end(), fLastDistance );
626                 if  ( aIter != rDistances.begin() )
627                 {
628                     --aIter;
629                     if ( ( *aIter > fDistance ) && ( *aIter < fLastDistance ) )
630                     {
631                         Point& rPt0 = rPoly[ i - 1 ];
632                         sal_Int32 fX = rPoint.X() - rPt0.X();
633                         sal_Int32 fY = rPoint.Y() - rPt0.Y();
634                         double fd = ( 1.0 / ( fDistance - fLastDistance ) ) * ( *aIter - fLastDistance );
635                         rPoly.Insert( i, Point( static_cast<sal_Int32>( rPt0.X() + fX * fd ), static_cast<sal_Int32>( rPt0.Y() + fY * fd ) ) );
636                         fDistance = *aIter;
637                     }
638                 }
639             }
640         }
641         fLastDistance = fDistance;
642     }
643 }
644 
645 static void GetPoint( const tools::Polygon& rPoly, const std::vector< double >& rDistances, const double& fX, double& fx1, double& fy1 )
646 {
647     fy1 = fx1 = 0.0;
648     if ( rPoly.GetSize() <= 1 )
649         return;
650 
651     std::vector< double >::const_iterator aIter = std::lower_bound( rDistances.begin(), rDistances.end(), fX );
652     sal_uInt16 nIdx = sal::static_int_cast<sal_uInt16>( std::distance( rDistances.begin(), aIter ) );
653     if ( aIter == rDistances.end() )
654         nIdx--;
655     const Point& rPt = rPoly[ nIdx ];
656     fx1 = rPt.X();
657     fy1 = rPt.Y();
658     if ( !nIdx || ( aIter == rDistances.end() ) || rtl::math::approxEqual( *aIter, fX ) )
659         return;
660 
661     nIdx = sal::static_int_cast<sal_uInt16>( std::distance( rDistances.begin(), aIter ) );
662     double fDist0 = *( aIter - 1 );
663     double fd = ( 1.0 / ( *aIter - fDist0 ) ) * ( fX - fDist0 );
664     const Point& rPt2 = rPoly[ nIdx - 1 ];
665     double fWidth = rPt.X() - rPt2.X();
666     double fHeight= rPt.Y() - rPt2.Y();
667     fWidth *= fd;
668     fHeight*= fd;
669     fx1 = rPt2.X() + fWidth;
670     fy1 = rPt2.Y() + fHeight;
671 }
672 
673 static void FitTextOutlinesToShapeOutlines( const tools::PolyPolygon& aOutlines2d, FWData& rFWData )
674 {
675     sal_uInt16 nOutline2dIdx = 0;
676     for( auto& rTextArea : rFWData.vTextAreas )
677     {
678         tools::Rectangle rTextAreaBoundRect = rTextArea.aBoundRect;
679         sal_Int32 nLeft = rTextAreaBoundRect.Left();
680         sal_Int32 nTop = rTextAreaBoundRect.Top();
681         sal_Int32 nWidth = rTextAreaBoundRect.GetWidth();
682         sal_Int32 nHeight= rTextAreaBoundRect.GetHeight();
683 
684         if (rFWData.bScaleX)
685         {
686             nWidth *= rFWData.fHorizontalTextScaling;
687         }
688 
689         if ( rFWData.bSingleLineMode && nHeight && nWidth )
690         {
691             if ( nOutline2dIdx >= aOutlines2d.Count() )
692                 break;
693             const tools::Polygon& rOutlinePoly( aOutlines2d[ nOutline2dIdx++ ] );
694             const sal_uInt16 nPointCount = rOutlinePoly.GetSize();
695             if ( nPointCount > 1 )
696             {
697                 std::vector< double > vDistances;
698                 vDistances.reserve( nPointCount );
699                 CalcDistances( rOutlinePoly, vDistances );
700                 if ( !vDistances.empty() )
701                 {
702                     for( auto& rParagraph : rTextArea.vParagraphs )
703                     {
704                         for ( auto& rCharacter : rParagraph.vCharacters )
705                         {
706                             for( tools::PolyPolygon& rPolyPoly : rCharacter.vOutlines )
707                             {
708                                 tools::Rectangle aBoundRect( rPolyPoly.GetBoundRect() );
709                                 double fx1 = aBoundRect.Left() - nLeft;
710                                 double fx2 = aBoundRect.Right() - nLeft;
711                                 double fy1, fy2;
712                                 double fM1 = fx1 / static_cast<double>(nWidth);
713                                 double fM2 = fx2 / static_cast<double>(nWidth);
714 
715                                 GetPoint( rOutlinePoly, vDistances, fM1, fx1, fy1 );
716                                 GetPoint( rOutlinePoly, vDistances, fM2, fx2, fy2 );
717 
718                                 double fvx = fy2 - fy1;
719                                 double fvy = - ( fx2 - fx1 );
720                                 fx1 = fx1 + ( ( fx2 - fx1 ) * 0.5 );
721                                 fy1 = fy1 + ( ( fy2 - fy1 ) * 0.5 );
722 
723                                 double fAngle = atan2( -fvx, -fvy );
724                                 double fL = hypot( fvx, fvy );
725                                 if (fL == 0.0)
726                                 {
727                                     SAL_WARN("svx", "FitTextOutlinesToShapeOutlines div-by-zero, abandon fit");
728                                     break;
729                                 }
730                                 fvx = fvx / fL;
731                                 fvy = fvy / fL;
732                                 fL = rTextArea.aBoundRect.GetHeight() / 2.0 + rTextArea.aBoundRect.Top() - rParagraph.aBoundRect.Center().Y();
733                                 fvx *= fL;
734                                 fvy *= fL;
735                                 rPolyPoly.Rotate( Point( aBoundRect.Center().X(), rParagraph.aBoundRect.Center().Y() ), sin( fAngle ), cos( fAngle ) );
736                                 rPolyPoly.Move( static_cast<sal_Int32>( ( fx1 + fvx )- aBoundRect.Center().X() ), static_cast<sal_Int32>( ( fy1 + fvy ) - rParagraph.aBoundRect.Center().Y() ) );
737                             }
738                         }
739                     }
740                 }
741             }
742         }
743         else
744         {
745             if ( ( nOutline2dIdx + 1 ) >= aOutlines2d.Count() )
746                 break;
747             const tools::Polygon& rOutlinePoly( aOutlines2d[ nOutline2dIdx++ ] );
748             const tools::Polygon& rOutlinePoly2( aOutlines2d[ nOutline2dIdx++ ] );
749             const sal_uInt16 nPointCount = rOutlinePoly.GetSize();
750             const sal_uInt16 nPointCount2 = rOutlinePoly2.GetSize();
751             if ( ( nPointCount > 1 ) && ( nPointCount2 > 1 ) )
752             {
753                 std::vector< double > vDistances;
754                 vDistances.reserve( nPointCount );
755                 std::vector< double > vDistances2;
756                 vDistances2.reserve( nPointCount2 );
757                 CalcDistances( rOutlinePoly, vDistances );
758                 CalcDistances( rOutlinePoly2, vDistances2 );
759                 for( auto& rParagraph : rTextArea.vParagraphs )
760                 {
761                     for ( auto& rCharacter : rParagraph.vCharacters )
762                     {
763                         for( tools::PolyPolygon& rPolyPoly : rCharacter.vOutlines )
764                         {
765                             sal_uInt16 i, nPolyCount = rPolyPoly.Count();
766                             for ( i = 0; i < nPolyCount; i++ )
767                             {
768                                 // #i35928#
769                                 basegfx::B2DPolygon aCandidate(rPolyPoly[ i ].getB2DPolygon());
770 
771                                 if(aCandidate.areControlPointsUsed())
772                                 {
773                                     aCandidate = basegfx::utils::adaptiveSubdivideByAngle(aCandidate);
774                                 }
775 
776                                 // create local polygon copy to work on
777                                 tools::Polygon aLocalPoly(aCandidate);
778 
779                                 InsertMissingOutlinePoints( vDistances, rTextAreaBoundRect, aLocalPoly );
780                                 InsertMissingOutlinePoints( vDistances2, rTextAreaBoundRect, aLocalPoly );
781 
782                                 sal_uInt16 _nPointCount = aLocalPoly.GetSize();
783                                 if (_nPointCount)
784                                 {
785                                     if (!nWidth || !nHeight)
786                                         throw o3tl::divide_by_zero();
787                                     for (sal_uInt16 j = 0; j < _nPointCount; ++j)
788                                     {
789                                         Point& rPoint = aLocalPoly[ j ];
790                                         rPoint.AdjustX( -nLeft );
791                                         rPoint.AdjustY( -nTop );
792                                         double fX = static_cast<double>(rPoint.X()) / static_cast<double>(nWidth);
793                                         double fY = static_cast<double>(rPoint.Y()) / static_cast<double>(nHeight);
794 
795                                         double fx1, fy1, fx2, fy2;
796                                         GetPoint( rOutlinePoly, vDistances, fX, fx1, fy1 );
797                                         GetPoint( rOutlinePoly2, vDistances2, fX, fx2, fy2 );
798                                         double fWidth = fx2 - fx1;
799                                         double fHeight= fy2 - fy1;
800                                         rPoint.setX( static_cast<sal_Int32>( fx1 + fWidth * fY ) );
801                                         rPoint.setY( static_cast<sal_Int32>( fy1 + fHeight* fY ) );
802                                     }
803                                 }
804 
805                                 // write back polygon
806                                 rPolyPoly[ i ] = aLocalPoly;
807                             }
808                         }
809                     }
810                 }
811             }
812         }
813     }
814 }
815 
816 static SdrObject* CreateSdrObjectFromParagraphOutlines(
817     const FWData& rFWData,
818     const SdrObjCustomShape& rSdrObjCustomShape)
819 {
820     SdrObject* pRet = nullptr;
821     basegfx::B2DPolyPolygon aPolyPoly;
822     if ( !rFWData.vTextAreas.empty() )
823     {
824         for ( const auto& rTextArea : rFWData.vTextAreas )
825         {
826             for ( const auto& rParagraph : rTextArea.vParagraphs )
827             {
828                 for ( const auto& rCharacter : rParagraph.vCharacters )
829                 {
830                     for( const auto& rOutline : rCharacter.vOutlines )
831                     {
832                         aPolyPoly.append( rOutline.getB2DPolyPolygon() );
833                     }
834                 }
835             }
836         }
837 
838         pRet = new SdrPathObj(
839             rSdrObjCustomShape.getSdrModelFromSdrObject(),
840             OBJ_POLY,
841             aPolyPoly);
842 
843         SfxItemSet aSet(rSdrObjCustomShape.GetMergedItemSet());
844         aSet.ClearItem( SDRATTR_TEXTDIRECTION );    //SJ: vertical writing is not required, by removing this item no outliner is created
845         aSet.Put(makeSdrShadowItem(false)); // #i37011# NO shadow for FontWork geometry
846         pRet->SetMergedItemSet( aSet );             // * otherwise we would crash, because the outliner tries to create a Paraobject, but there is no model
847     }
848 
849     return pRet;
850 }
851 
852 Reference < i18n::XBreakIterator > EnhancedCustomShapeFontWork::mxBreakIterator;
853 
854 Reference < i18n::XBreakIterator > const & EnhancedCustomShapeFontWork::GetBreakIterator()
855 {
856     if ( !mxBreakIterator.is() )
857     {
858         Reference< uno::XComponentContext > xContext = ::comphelper::getProcessComponentContext();
859         mxBreakIterator = i18n::BreakIterator::create(xContext);
860     }
861     return mxBreakIterator;
862 }
863 
864 SdrObject* EnhancedCustomShapeFontWork::CreateFontWork(
865     const SdrObject* pShape2d,
866     const SdrObjCustomShape& rSdrObjCustomShape)
867 {
868     SdrObject* pRet = nullptr;
869 
870     tools::PolyPolygon aOutlines2d( GetOutlinesFromShape2d( pShape2d ) );
871     sal_uInt16 nOutlinesCount2d = aOutlines2d.Count();
872     if ( nOutlinesCount2d )
873     {
874         FWData aFWData;
875 
876         if(InitializeFontWorkData(rSdrObjCustomShape, nOutlinesCount2d, aFWData))
877         {
878             /* retrieves the horizontal scaling factor that has to be used
879             to fit each paragraph text into its corresponding 2d outline */
880             CalculateHorizontalScalingFactor(
881                 rSdrObjCustomShape,
882                 aFWData,
883                 aOutlines2d);
884 
885             /* retrieving the Outlines for the each Paragraph. */
886             if(!GetFontWorkOutline(
887                 aFWData,
888                 rSdrObjCustomShape))
889             {
890                 return nullptr;
891             }
892 
893             FitTextOutlinesToShapeOutlines( aOutlines2d, aFWData );
894 
895             pRet = CreateSdrObjectFromParagraphOutlines(
896                 aFWData,
897                 rSdrObjCustomShape);
898         }
899     }
900     return pRet;
901 }
902 
903 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */
904