1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
3 * This file is part of the LibreOffice project.
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/.
9 * This file incorporates work covered by the following license notice:
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 .
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>
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>
53 using namespace com::sun::star
;
54 using namespace com::sun::star::uno
;
58 struct FWCharacterData
// representing a single character
60 std::vector
< tools::PolyPolygon
> vOutlines
;
61 tools::Rectangle aBoundRect
;
63 struct FWParagraphData
// representing a single paragraph
66 std::vector
< FWCharacterData
> vCharacters
;
67 tools::Rectangle aBoundRect
;
68 SvxFrameDirection nFrameDirection
;
70 struct FWTextArea
// representing multiple concluding paragraphs
72 std::vector
< FWParagraphData
> vParagraphs
;
73 tools::Rectangle aBoundRect
;
75 struct FWData
// representing the whole text
77 std::vector
< FWTextArea
> vTextAreas
;
78 double fHorizontalTextScaling
;
79 double fVerticalTextScaling
;
80 sal_uInt32 nMaxParagraphsPerTextArea
;
81 sal_Int32 nSingleLineHeight
;
88 static bool InitializeFontWorkData(
89 const SdrObjCustomShape
& rSdrObjCustomShape
,
90 const sal_uInt16 nOutlinesCount2d
,
94 bool bSingleLineMode
= false;
95 sal_uInt16 nTextAreaCount
= nOutlinesCount2d
;
96 if ( nOutlinesCount2d
& 1 )
97 bSingleLineMode
= true;
101 const SdrCustomShapeGeometryItem
& rGeometryItem( rSdrObjCustomShape
.GetMergedItem( SDRATTR_CUSTOMSHAPE_GEOMETRY
) );
102 const css::uno::Any
* pAny
= rGeometryItem
.GetPropertyValueByName( "TextPath", "ScaleX" );
104 *pAny
>>= rFWData
.bScaleX
;
106 rFWData
.bScaleX
= false;
108 if ( nTextAreaCount
)
110 rFWData
.bSingleLineMode
= bSingleLineMode
;
112 // setting the strings
113 OutlinerParaObject
* pParaObj(rSdrObjCustomShape
.GetOutlinerParaObject());
117 const EditTextObject
& rTextObj
= pParaObj
->GetTextObject();
118 sal_Int32 nParagraphsLeft
= rTextObj
.GetParagraphCount();
120 rFWData
.nMaxParagraphsPerTextArea
= ( ( nParagraphsLeft
- 1 ) / nTextAreaCount
) + 1;
122 while( nParagraphsLeft
&& nTextAreaCount
)
124 FWTextArea aTextArea
;
125 sal_Int32 i
, nParagraphs
= ( ( nParagraphsLeft
- 1 ) / nTextAreaCount
) + 1;
126 for ( i
= 0; i
< nParagraphs
; ++i
, ++j
)
128 FWParagraphData aParagraphData
;
129 aParagraphData
.aString
= rTextObj
.GetText( j
);
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
);
135 rFWData
.vTextAreas
.push_back( aTextArea
);
136 nParagraphsLeft
-= nParagraphs
;
145 static double GetLength( const tools::Polygon
& rPolygon
)
148 if ( rPolygon
.GetSize() > 1 )
150 sal_uInt16 nCount
= rPolygon
.GetSize();
152 fLength
+= rPolygon
.CalcDistance( nCount
, nCount
- 1 );
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
,
163 const tools::PolyPolygon
& rOutline2d
)
165 double fScalingFactor
= 1.0;
166 rFWData
.fVerticalTextScaling
= 1.0;
169 bool bSingleLineMode
= false;
170 sal_uInt16 nOutlinesCount2d
= rOutline2d
.Count();
173 const SvxFontItem
& rFontItem( rSdrObjCustomShape
.GetMergedItem( EE_CHAR_FONTINFO
) );
174 const SvxFontHeightItem
& rFontHeight( rSdrObjCustomShape
.GetMergedItem( EE_CHAR_FONTHEIGHT
) );
175 sal_Int32 nFontSize
= rFontHeight
.GetHeight();
178 aFont
.SetFontHeight( nFontSize
);
180 aFont
.SetFontHeight( rSdrObjCustomShape
.GetLogicRect().GetHeight() / rFWData
.nMaxParagraphsPerTextArea
);
182 aFont
.SetAlignment( ALIGN_TOP
);
183 aFont
.SetFamilyName( rFontItem
.GetFamilyName() );
184 aFont
.SetFamily( rFontItem
.GetFamily() );
185 aFont
.SetStyleName( rFontItem
.GetStyleName() );
186 const SvxPostureItem
& rPostureItem
= rSdrObjCustomShape
.GetMergedItem( EE_CHAR_ITALIC
);
187 aFont
.SetItalic( rPostureItem
.GetPosture() );
189 const SvxWeightItem
& rWeightItem
= rSdrObjCustomShape
.GetMergedItem( EE_CHAR_WEIGHT
);
190 aFont
.SetWeight( rWeightItem
.GetWeight() );
191 aFont
.SetOrientation( 0_deg10
);
192 // initializing virtual device
194 ScopedVclPtrInstance
< VirtualDevice
> pVirDev(DeviceFormat::DEFAULT
);
195 pVirDev
->SetMapMode(MapMode(MapUnit::Map100thMM
));
196 pVirDev
->SetFont( aFont
);
198 if ( nOutlinesCount2d
& 1 )
199 bSingleLineMode
= true;
201 // In case of rFWData.bScaleX == true it loops with reduced font size until the current run
202 // results in a fScalingFactor >=1.0. The fact, that case rFWData.bScaleX == true keeps font
203 // size if possible, is not done here with scaling factor 1 but is done in method
204 // FitTextOutlinesToShapeOutlines()
208 bool bScalingFactorDefined
= false; // New calculation for each font size
209 for( const auto& rTextArea
: rFWData
.vTextAreas
)
211 // calculating the width of the corresponding 2d text area
212 double fWidth
= GetLength( rOutline2d
.GetObject( i
++ ) );
213 if ( !bSingleLineMode
)
215 fWidth
+= GetLength( rOutline2d
.GetObject( i
++ ) );
219 for( const auto& rParagraph
: rTextArea
.vParagraphs
)
221 double fTextWidth
= pVirDev
->GetTextWidth( rParagraph
.aString
);
222 if ( fTextWidth
> 0.0 )
224 double fScale
= fWidth
/ fTextWidth
;
225 if ( !bScalingFactorDefined
)
227 fScalingFactor
= fScale
;
228 bScalingFactorDefined
= true;
230 else if (fScale
< fScalingFactor
)
232 fScalingFactor
= fScale
;
238 if (fScalingFactor
< 1.0)
241 aFont
.SetFontHeight( nFontSize
);
242 pVirDev
->SetFont( aFont
);
245 while (rFWData
.bScaleX
&& fScalingFactor
< 1.0 && nFontSize
> 1 );
248 rFWData
.fVerticalTextScaling
= static_cast<double>(nFontSize
) / rFontHeight
.GetHeight();
250 rFWData
.fHorizontalTextScaling
= fScalingFactor
;
253 static void GetTextAreaOutline(
254 const FWData
& rFWData
,
255 const SdrObjCustomShape
& rSdrObjCustomShape
,
256 FWTextArea
& rTextArea
,
257 bool bSameLetterHeights
)
259 bool bIsVertical(rSdrObjCustomShape
.IsVerticalWriting());
260 sal_Int32 nVerticalOffset
= rFWData
.nMaxParagraphsPerTextArea
> rTextArea
.vParagraphs
.size()
261 ? rFWData
.nSingleLineHeight
/ 2 : 0;
263 for( auto& rParagraph
: rTextArea
.vParagraphs
)
265 const OUString
& rText
= rParagraph
.aString
;
266 if ( !rText
.isEmpty() )
268 // generating vcl/font
269 sal_uInt16 nScriptType
= i18n::ScriptType::LATIN
;
270 Reference
< i18n::XBreakIterator
> xBI( EnhancedCustomShapeFontWork::GetBreakIterator() );
273 nScriptType
= xBI
->getScriptType( rText
, 0 );
274 if( i18n::ScriptType::WEAK
== nScriptType
)
276 sal_Int32 nChg
= xBI
->endOfScript( rText
, 0, nScriptType
);
277 if (nChg
< rText
.getLength() && nChg
>= 0)
278 nScriptType
= xBI
->getScriptType( rText
, nChg
);
280 nScriptType
= i18n::ScriptType::LATIN
;
283 sal_uInt16 nFntItm
= EE_CHAR_FONTINFO
;
284 if ( nScriptType
== i18n::ScriptType::COMPLEX
)
285 nFntItm
= EE_CHAR_FONTINFO_CTL
;
286 else if ( nScriptType
== i18n::ScriptType::ASIAN
)
287 nFntItm
= EE_CHAR_FONTINFO_CJK
;
288 const SvxFontItem
& rFontItem
= static_cast<const SvxFontItem
&>(rSdrObjCustomShape
.GetMergedItem( nFntItm
));
291 aFont
.SetFontHeight( rFWData
.nSingleLineHeight
);
293 aFont
.SetAlignment( ALIGN_TOP
);
295 aFont
.SetFamilyName( rFontItem
.GetFamilyName() );
296 aFont
.SetFamily( rFontItem
.GetFamily() );
297 aFont
.SetStyleName( rFontItem
.GetStyleName() );
298 aFont
.SetOrientation( 0_deg10
);
300 const SvxPostureItem
& rPostureItem
= rSdrObjCustomShape
.GetMergedItem( EE_CHAR_ITALIC
);
301 aFont
.SetItalic( rPostureItem
.GetPosture() );
303 const SvxWeightItem
& rWeightItem
= rSdrObjCustomShape
.GetMergedItem( EE_CHAR_WEIGHT
);
304 aFont
.SetWeight( rWeightItem
.GetWeight() );
306 // initializing virtual device
307 ScopedVclPtrInstance
< VirtualDevice
> pVirDev(DeviceFormat::DEFAULT
);
308 pVirDev
->SetMapMode(MapMode(MapUnit::Map100thMM
));
309 pVirDev
->SetFont( aFont
);
310 pVirDev
->EnableRTL();
311 if ( rParagraph
.nFrameDirection
== SvxFrameDirection::Horizontal_RL_TB
)
312 pVirDev
->SetLayoutMode( ComplexTextLayoutFlags::BiDiRtl
);
314 const SvxCharScaleWidthItem
& rCharScaleWidthItem
= rSdrObjCustomShape
.GetMergedItem( EE_CHAR_FONTWIDTH
);
315 sal_uInt16 nCharScaleWidth
= rCharScaleWidthItem
.GetValue();
316 std::unique_ptr
<tools::Long
[]> pDXArry
;
317 sal_Int32 nWidth
= 0;
322 // vertical _> each single character needs to be rotated by 90
324 sal_Int32 nHeight
= 0;
325 tools::Rectangle aSingleCharacterUnion
;
326 for ( i
= 0; i
< rText
.getLength(); i
++ )
328 FWCharacterData aCharacterData
;
329 OUString
aCharText( rText
[ i
] );
330 if ( pVirDev
->GetTextOutlines( aCharacterData
.vOutlines
, aCharText
, 0, 0, -1, nWidth
, pDXArry
.get() ) )
332 sal_Int32 nTextWidth
= pVirDev
->GetTextWidth( aCharText
);
333 if ( aCharacterData
.vOutlines
.empty() )
335 nHeight
+= rFWData
.nSingleLineHeight
;
339 for ( auto& rOutline
: aCharacterData
.vOutlines
)
342 rOutline
.Rotate( Point( nTextWidth
/ 2, rFWData
.nSingleLineHeight
/ 2 ), 900_deg10
);
343 aCharacterData
.aBoundRect
.Union( rOutline
.GetBoundRect() );
345 for ( auto& rOutline
: aCharacterData
.vOutlines
)
347 sal_Int32 nM
= - aCharacterData
.aBoundRect
.Left() + nHeight
;
348 rOutline
.Move( nM
, 0 );
349 aCharacterData
.aBoundRect
.Move( nM
, 0 );
351 nHeight
+= aCharacterData
.aBoundRect
.GetWidth() + ( rFWData
.nSingleLineHeight
/ 5 );
352 aSingleCharacterUnion
.Union( aCharacterData
.aBoundRect
);
355 rParagraph
.vCharacters
.push_back( aCharacterData
);
357 for ( auto& rCharacter
: rParagraph
.vCharacters
)
359 for ( auto& rOutline
: rCharacter
.vOutlines
)
361 rOutline
.Move( ( aSingleCharacterUnion
.GetWidth() - rCharacter
.aBoundRect
.GetWidth() ) / 2, 0 );
367 if ( ( nCharScaleWidth
!= 100 ) && nCharScaleWidth
)
368 { // applying character spacing
369 pDXArry
.reset(new tools::Long
[ rText
.getLength() ]);
370 pVirDev
->GetTextArray( rText
, pDXArry
.get());
371 FontMetric
aFontMetric( pVirDev
->GetFontMetric() );
372 aFont
.SetAverageFontWidth( static_cast<sal_Int32
>( static_cast<double>(aFontMetric
.GetAverageFontWidth()) * ( double(100) / static_cast<double>(nCharScaleWidth
) ) ) );
373 pVirDev
->SetFont( aFont
);
375 FWCharacterData aCharacterData
;
376 if ( pVirDev
->GetTextOutlines( aCharacterData
.vOutlines
, rText
, 0, 0, -1, nWidth
, pDXArry
.get() ) )
378 rParagraph
.vCharacters
.push_back( aCharacterData
);
382 // vertical alignment
383 for ( auto& rCharacter
: rParagraph
.vCharacters
)
385 for( tools::PolyPolygon
& rPolyPoly
: rCharacter
.vOutlines
)
387 if ( nVerticalOffset
)
388 rPolyPoly
.Move( 0, nVerticalOffset
);
390 // retrieving the boundrect for the paragraph
391 tools::Rectangle
aBoundRect( rPolyPoly
.GetBoundRect() );
392 rParagraph
.aBoundRect
.Union( aBoundRect
);
396 // updating the boundrect for the text area by merging the current paragraph boundrect
397 if ( rParagraph
.aBoundRect
.IsEmpty() )
399 if ( rTextArea
.aBoundRect
.IsEmpty() )
400 rTextArea
.aBoundRect
= tools::Rectangle( Point( 0, 0 ), Size( 1, rFWData
.nSingleLineHeight
) );
402 rTextArea
.aBoundRect
.AdjustBottom(rFWData
.nSingleLineHeight
);
406 tools::Rectangle
& rParagraphBoundRect
= rParagraph
.aBoundRect
;
407 rTextArea
.aBoundRect
.Union( rParagraphBoundRect
);
409 if ( bSameLetterHeights
)
411 for ( auto& rCharacter
: rParagraph
.vCharacters
)
413 for( auto& rOutline
: rCharacter
.vOutlines
)
415 tools::Rectangle
aPolyPolyBoundRect( rOutline
.GetBoundRect() );
416 if (aPolyPolyBoundRect
.GetHeight() != rParagraphBoundRect
.GetHeight() && aPolyPolyBoundRect
.GetHeight())
417 rOutline
.Scale( 1.0, static_cast<double>(rParagraphBoundRect
.GetHeight()) / aPolyPolyBoundRect
.GetHeight() );
418 aPolyPolyBoundRect
= rOutline
.GetBoundRect();
419 sal_Int32 nMove
= aPolyPolyBoundRect
.Top() - rParagraphBoundRect
.Top();
421 rOutline
.Move( 0, -nMove
);
427 nVerticalOffset
-= rFWData
.nSingleLineHeight
;
429 nVerticalOffset
+= rFWData
.nSingleLineHeight
;
433 static bool GetFontWorkOutline(
435 const SdrObjCustomShape
& rSdrObjCustomShape
)
437 SdrTextHorzAdjust
eHorzAdjust(rSdrObjCustomShape
.GetMergedItem( SDRATTR_TEXT_HORZADJUST
).GetValue());
438 drawing::TextFitToSizeType
const eFTS(rSdrObjCustomShape
.GetMergedItem( SDRATTR_TEXT_FITTOSIZE
).GetValue());
440 bool bSameLetterHeights
= false;
441 const SdrCustomShapeGeometryItem
& rGeometryItem(rSdrObjCustomShape
.GetMergedItem( SDRATTR_CUSTOMSHAPE_GEOMETRY
));
442 const css::uno::Any
* pAny
= rGeometryItem
.GetPropertyValueByName( "TextPath", "SameLetterHeights" );
444 *pAny
>>= bSameLetterHeights
;
446 const SvxFontHeightItem
& rFontHeight( rSdrObjCustomShape
.GetMergedItem( EE_CHAR_FONTHEIGHT
) );
448 rFWData
.nSingleLineHeight
= rFWData
.fVerticalTextScaling
* rFontHeight
.GetHeight();
450 rFWData
.nSingleLineHeight
= static_cast<sal_Int32
>( ( static_cast<double>( rSdrObjCustomShape
.GetLogicRect().GetHeight() )
451 / rFWData
.nMaxParagraphsPerTextArea
) * rFWData
.fHorizontalTextScaling
);
453 if (rFWData
.nSingleLineHeight
== SAL_MIN_INT32
)
456 for ( auto& rTextArea
: rFWData
.vTextAreas
)
464 if (eFTS
== drawing::TextFitToSizeType_ALLLINES
||
465 // tdf#97630 interpret PROPORTIONAL same as ALLLINES so we don't
466 // need another ODF attribute!
467 eFTS
== drawing::TextFitToSizeType_PROPORTIONAL
)
469 for ( auto& rParagraph
: rTextArea
.vParagraphs
)
471 sal_Int32 nParaWidth
= rParagraph
.aBoundRect
.GetWidth();
474 double fScale
= static_cast<double>(rTextArea
.aBoundRect
.GetWidth()) / nParaWidth
;
476 for ( auto& rCharacter
: rParagraph
.vCharacters
)
478 for( auto& rOutline
: rCharacter
.vOutlines
)
480 rOutline
.Scale( fScale
, 1.0 );
486 else if (rFWData
.bScaleX
)
488 const SdrTextVertAdjust nVertJustify
= rSdrObjCustomShape
.GetMergedItem( SDRATTR_TEXT_VERTADJUST
).GetValue();
489 double fFactor
= nVertJustify
== SdrTextVertAdjust::SDRTEXTVERTADJUST_BOTTOM
? -0.5 : ( nVertJustify
== SdrTextVertAdjust::SDRTEXTVERTADJUST_TOP
? 0.5 : 0 );
491 for ( auto& rParagraph
: rTextArea
.vParagraphs
)
493 sal_Int32 nHorzDiff
= 0;
494 sal_Int32 nVertDiff
= static_cast<double>( rFWData
.nSingleLineHeight
) * fFactor
* ( rTextArea
.vParagraphs
.size() - 1 );
496 if ( eHorzAdjust
== SDRTEXTHORZADJUST_CENTER
)
497 nHorzDiff
= ( rFWData
.fHorizontalTextScaling
* rTextArea
.aBoundRect
.GetWidth() - rParagraph
.aBoundRect
.GetWidth() ) / 2;
498 else if ( eHorzAdjust
== SDRTEXTHORZADJUST_RIGHT
)
499 nHorzDiff
= ( rFWData
.fHorizontalTextScaling
* rTextArea
.aBoundRect
.GetWidth() - rParagraph
.aBoundRect
.GetWidth() );
501 if (nHorzDiff
|| nVertDiff
)
503 for ( auto& rCharacter
: rParagraph
.vCharacters
)
505 for( auto& rOutline
: rCharacter
.vOutlines
)
507 rOutline
.Move( nHorzDiff
, nVertDiff
);
515 switch( eHorzAdjust
)
517 case SDRTEXTHORZADJUST_RIGHT
:
518 case SDRTEXTHORZADJUST_CENTER
:
520 for ( auto& rParagraph
: rTextArea
.vParagraphs
)
522 sal_Int32 nHorzDiff
= 0;
523 if ( eHorzAdjust
== SDRTEXTHORZADJUST_CENTER
)
524 nHorzDiff
= ( rTextArea
.aBoundRect
.GetWidth() - rParagraph
.aBoundRect
.GetWidth() ) / 2;
525 else if ( eHorzAdjust
== SDRTEXTHORZADJUST_RIGHT
)
526 nHorzDiff
= ( rTextArea
.aBoundRect
.GetWidth() - rParagraph
.aBoundRect
.GetWidth() );
529 for ( auto& rCharacter
: rParagraph
.vCharacters
)
531 for( auto& rOutline
: rCharacter
.vOutlines
)
533 rOutline
.Move( nHorzDiff
, 0 );
541 case SDRTEXTHORZADJUST_BLOCK
: break; // don't know
542 case SDRTEXTHORZADJUST_LEFT
: break; // already left aligned -> nothing to do
550 static basegfx::B2DPolyPolygon
GetOutlinesFromShape2d( const SdrObject
* pShape2d
)
552 basegfx::B2DPolyPolygon aOutlines2d
;
554 SdrObjListIter
aObjListIter( *pShape2d
, SdrIterMode::DeepWithGroups
);
555 while( aObjListIter
.IsMore() )
557 SdrObject
* pPartObj
= aObjListIter
.Next();
558 if ( auto pPathObj
= dynamic_cast<const SdrPathObj
*>( pPartObj
))
560 basegfx::B2DPolyPolygon
aCandidate(pPathObj
->GetPathPoly());
561 if(aCandidate
.areControlPointsUsed())
563 aCandidate
= basegfx::utils::adaptiveSubdivideByAngle(aCandidate
);
565 aOutlines2d
.append(aCandidate
);
572 static void CalcDistances( const tools::Polygon
& rPoly
, std::vector
< double >& rDistances
)
574 sal_uInt16 i
, nCount
= rPoly
.GetSize();
578 for ( i
= 0; i
< nCount
; i
++ )
580 double fDistance
= i
? rPoly
.CalcDistance( i
, i
- 1 ) : 0.0;
581 rDistances
.push_back( fDistance
);
583 std::partial_sum( rDistances
.begin(), rDistances
.end(), rDistances
.begin() );
584 double fLength
= rDistances
[ rDistances
.size() - 1 ];
587 for ( auto& rDistance
: rDistances
)
588 rDistance
/= fLength
;
592 static void InsertMissingOutlinePoints( const std::vector
< double >& rDistances
,
593 const tools::Rectangle
& rTextAreaBoundRect
, tools::Polygon
& rPoly
)
595 sal_uInt16 nSize
= rPoly
.GetSize();
599 tools::Long nTextWidth
= rTextAreaBoundRect
.GetWidth();
602 throw o3tl::divide_by_zero();
604 double fLastDistance
= 0.0;
605 for (sal_uInt16 i
= 0; i
< nSize
; ++i
)
607 Point
& rPoint
= rPoly
[ i
];
608 double fDistance
= static_cast<double>( rPoint
.X() - rTextAreaBoundRect
.Left() ) / static_cast<double>(nTextWidth
);
611 if ( fDistance
> fLastDistance
)
613 std::vector
< double >::const_iterator aIter
= std::upper_bound( rDistances
.begin(), rDistances
.end(), fLastDistance
);
614 if ( aIter
!= rDistances
.end() && ( *aIter
> fLastDistance
) && ( *aIter
< fDistance
) )
616 Point
& rPt0
= rPoly
[ i
- 1 ];
617 sal_Int32 fX
= rPoint
.X() - rPt0
.X();
618 sal_Int32 fY
= rPoint
.Y() - rPt0
.Y();
619 double fd
= ( 1.0 / ( fDistance
- fLastDistance
) ) * ( *aIter
- fLastDistance
);
620 rPoly
.Insert( i
, Point( static_cast<sal_Int32
>( rPt0
.X() + fX
* fd
), static_cast<sal_Int32
>( rPt0
.Y() + fY
* fd
) ) );
624 else if ( fDistance
< fLastDistance
)
626 std::vector
< double >::const_iterator aIter
= std::lower_bound( rDistances
.begin(), rDistances
.end(), fLastDistance
);
627 if ( aIter
!= rDistances
.begin() )
630 if ( ( *aIter
> fDistance
) && ( *aIter
< fLastDistance
) )
632 Point
& rPt0
= rPoly
[ i
- 1 ];
633 sal_Int32 fX
= rPoint
.X() - rPt0
.X();
634 sal_Int32 fY
= rPoint
.Y() - rPt0
.Y();
635 double fd
= ( 1.0 / ( fDistance
- fLastDistance
) ) * ( *aIter
- fLastDistance
);
636 rPoly
.Insert( i
, Point( static_cast<sal_Int32
>( rPt0
.X() + fX
* fd
), static_cast<sal_Int32
>( rPt0
.Y() + fY
* fd
) ) );
642 fLastDistance
= fDistance
;
646 static void GetPoint( const tools::Polygon
& rPoly
, const std::vector
< double >& rDistances
, const double& fX
, double& fx1
, double& fy1
)
649 if ( rPoly
.GetSize() <= 1 )
652 std::vector
< double >::const_iterator aIter
= std::lower_bound( rDistances
.begin(), rDistances
.end(), fX
);
653 sal_uInt16 nIdx
= sal::static_int_cast
<sal_uInt16
>( std::distance( rDistances
.begin(), aIter
) );
654 if ( aIter
== rDistances
.end() )
656 const Point
& rPt
= rPoly
[ nIdx
];
659 if ( !nIdx
|| ( aIter
== rDistances
.end() ) || rtl::math::approxEqual( *aIter
, fX
) )
662 nIdx
= sal::static_int_cast
<sal_uInt16
>( std::distance( rDistances
.begin(), aIter
) );
663 double fDist0
= *( aIter
- 1 );
664 double fd
= ( 1.0 / ( *aIter
- fDist0
) ) * ( fX
- fDist0
);
665 const Point
& rPt2
= rPoly
[ nIdx
- 1 ];
666 double fWidth
= rPt
.X() - rPt2
.X();
667 double fHeight
= rPt
.Y() - rPt2
.Y();
670 fx1
= rPt2
.X() + fWidth
;
671 fy1
= rPt2
.Y() + fHeight
;
674 static void FitTextOutlinesToShapeOutlines( const tools::PolyPolygon
& aOutlines2d
, FWData
& rFWData
)
676 sal_uInt16 nOutline2dIdx
= 0;
677 for( auto& rTextArea
: rFWData
.vTextAreas
)
679 tools::Rectangle rTextAreaBoundRect
= rTextArea
.aBoundRect
;
680 sal_Int32 nLeft
= rTextAreaBoundRect
.Left();
681 sal_Int32 nTop
= rTextAreaBoundRect
.Top();
682 sal_Int32 nWidth
= rTextAreaBoundRect
.GetWidth();
683 sal_Int32 nHeight
= rTextAreaBoundRect
.GetHeight();
687 nWidth
*= rFWData
.fHorizontalTextScaling
;
690 if ( rFWData
.bSingleLineMode
&& nHeight
&& nWidth
)
692 if ( nOutline2dIdx
>= aOutlines2d
.Count() )
694 const tools::Polygon
& rOutlinePoly( aOutlines2d
[ nOutline2dIdx
++ ] );
695 const sal_uInt16 nPointCount
= rOutlinePoly
.GetSize();
696 if ( nPointCount
> 1 )
698 std::vector
< double > vDistances
;
699 vDistances
.reserve( nPointCount
);
700 CalcDistances( rOutlinePoly
, vDistances
);
701 if ( !vDistances
.empty() )
703 for( auto& rParagraph
: rTextArea
.vParagraphs
)
705 for ( auto& rCharacter
: rParagraph
.vCharacters
)
707 for( tools::PolyPolygon
& rPolyPoly
: rCharacter
.vOutlines
)
709 tools::Rectangle
aBoundRect( rPolyPoly
.GetBoundRect() );
710 double fx1
= aBoundRect
.Left() - nLeft
;
711 double fx2
= aBoundRect
.Right() - nLeft
;
713 double fM1
= fx1
/ static_cast<double>(nWidth
);
714 double fM2
= fx2
/ static_cast<double>(nWidth
);
716 GetPoint( rOutlinePoly
, vDistances
, fM1
, fx1
, fy1
);
717 GetPoint( rOutlinePoly
, vDistances
, fM2
, fx2
, fy2
);
719 double fvx
= fy2
- fy1
;
720 double fvy
= - ( fx2
- fx1
);
721 fx1
= fx1
+ ( ( fx2
- fx1
) * 0.5 );
722 fy1
= fy1
+ ( ( fy2
- fy1
) * 0.5 );
724 double fAngle
= atan2( -fvx
, -fvy
);
725 double fL
= hypot( fvx
, fvy
);
728 SAL_WARN("svx", "FitTextOutlinesToShapeOutlines div-by-zero, abandon fit");
733 fL
= rTextArea
.aBoundRect
.GetHeight() / 2.0 + rTextArea
.aBoundRect
.Top() - rParagraph
.aBoundRect
.Center().Y();
736 rPolyPoly
.Rotate( Point( aBoundRect
.Center().X(), rParagraph
.aBoundRect
.Center().Y() ), sin( fAngle
), cos( fAngle
) );
737 rPolyPoly
.Move( static_cast<sal_Int32
>( ( fx1
+ fvx
)- aBoundRect
.Center().X() ), static_cast<sal_Int32
>( ( fy1
+ fvy
) - rParagraph
.aBoundRect
.Center().Y() ) );
746 if ( ( nOutline2dIdx
+ 1 ) >= aOutlines2d
.Count() )
748 const tools::Polygon
& rOutlinePoly( aOutlines2d
[ nOutline2dIdx
++ ] );
749 const tools::Polygon
& rOutlinePoly2( aOutlines2d
[ nOutline2dIdx
++ ] );
750 const sal_uInt16 nPointCount
= rOutlinePoly
.GetSize();
751 const sal_uInt16 nPointCount2
= rOutlinePoly2
.GetSize();
752 if ( ( nPointCount
> 1 ) && ( nPointCount2
> 1 ) )
754 std::vector
< double > vDistances
;
755 vDistances
.reserve( nPointCount
);
756 std::vector
< double > vDistances2
;
757 vDistances2
.reserve( nPointCount2
);
758 CalcDistances( rOutlinePoly
, vDistances
);
759 CalcDistances( rOutlinePoly2
, vDistances2
);
760 for( auto& rParagraph
: rTextArea
.vParagraphs
)
762 for ( auto& rCharacter
: rParagraph
.vCharacters
)
764 for( tools::PolyPolygon
& rPolyPoly
: rCharacter
.vOutlines
)
766 sal_uInt16 i
, nPolyCount
= rPolyPoly
.Count();
767 for ( i
= 0; i
< nPolyCount
; i
++ )
770 basegfx::B2DPolygon
aCandidate(rPolyPoly
[ i
].getB2DPolygon());
772 if(aCandidate
.areControlPointsUsed())
774 aCandidate
= basegfx::utils::adaptiveSubdivideByAngle(aCandidate
);
777 // create local polygon copy to work on
778 tools::Polygon
aLocalPoly(aCandidate
);
780 InsertMissingOutlinePoints( vDistances
, rTextAreaBoundRect
, aLocalPoly
);
781 InsertMissingOutlinePoints( vDistances2
, rTextAreaBoundRect
, aLocalPoly
);
783 sal_uInt16 _nPointCount
= aLocalPoly
.GetSize();
786 if (!nWidth
|| !nHeight
)
787 throw o3tl::divide_by_zero();
788 for (sal_uInt16 j
= 0; j
< _nPointCount
; ++j
)
790 Point
& rPoint
= aLocalPoly
[ j
];
791 rPoint
.AdjustX( -nLeft
);
792 rPoint
.AdjustY( -nTop
);
793 double fX
= static_cast<double>(rPoint
.X()) / static_cast<double>(nWidth
);
794 double fY
= static_cast<double>(rPoint
.Y()) / static_cast<double>(nHeight
);
796 double fx1
, fy1
, fx2
, fy2
;
797 GetPoint( rOutlinePoly
, vDistances
, fX
, fx1
, fy1
);
798 GetPoint( rOutlinePoly2
, vDistances2
, fX
, fx2
, fy2
);
799 double fWidth
= fx2
- fx1
;
800 double fHeight
= fy2
- fy1
;
801 rPoint
.setX( static_cast<sal_Int32
>( fx1
+ fWidth
* fY
) );
802 rPoint
.setY( static_cast<sal_Int32
>( fy1
+ fHeight
* fY
) );
806 // write back polygon
807 rPolyPoly
[ i
] = aLocalPoly
;
817 static SdrObject
* CreateSdrObjectFromParagraphOutlines(
818 const FWData
& rFWData
,
819 const SdrObjCustomShape
& rSdrObjCustomShape
)
821 SdrObject
* pRet
= nullptr;
822 basegfx::B2DPolyPolygon aPolyPoly
;
823 if ( !rFWData
.vTextAreas
.empty() )
825 for ( const auto& rTextArea
: rFWData
.vTextAreas
)
827 for ( const auto& rParagraph
: rTextArea
.vParagraphs
)
829 for ( const auto& rCharacter
: rParagraph
.vCharacters
)
831 for( const auto& rOutline
: rCharacter
.vOutlines
)
833 aPolyPoly
.append( rOutline
.getB2DPolyPolygon() );
839 pRet
= new SdrPathObj(
840 rSdrObjCustomShape
.getSdrModelFromSdrObject(),
844 SfxItemSet
aSet(rSdrObjCustomShape
.GetMergedItemSet());
845 aSet
.ClearItem( SDRATTR_TEXTDIRECTION
); //SJ: vertical writing is not required, by removing this item no outliner is created
846 aSet
.Put(makeSdrShadowItem(false)); // #i37011# NO shadow for FontWork geometry
847 pRet
->SetMergedItemSet( aSet
); // * otherwise we would crash, because the outliner tries to create a Paraobject, but there is no model
853 Reference
< i18n::XBreakIterator
> EnhancedCustomShapeFontWork::mxBreakIterator
;
855 Reference
< i18n::XBreakIterator
> const & EnhancedCustomShapeFontWork::GetBreakIterator()
857 if ( !mxBreakIterator
.is() )
859 Reference
< uno::XComponentContext
> xContext
= ::comphelper::getProcessComponentContext();
860 mxBreakIterator
= i18n::BreakIterator::create(xContext
);
862 return mxBreakIterator
;
865 SdrObject
* EnhancedCustomShapeFontWork::CreateFontWork(
866 const SdrObject
* pShape2d
,
867 const SdrObjCustomShape
& rSdrObjCustomShape
)
869 SdrObject
* pRet
= nullptr;
871 tools::PolyPolygon
aOutlines2d( GetOutlinesFromShape2d( pShape2d
) );
872 sal_uInt16 nOutlinesCount2d
= aOutlines2d
.Count();
873 if ( nOutlinesCount2d
)
877 if(InitializeFontWorkData(rSdrObjCustomShape
, nOutlinesCount2d
, aFWData
))
879 /* retrieves the horizontal scaling factor that has to be used
880 to fit each paragraph text into its corresponding 2d outline */
881 CalculateHorizontalScalingFactor(
886 /* retrieving the Outlines for the each Paragraph. */
887 if(!GetFontWorkOutline(
894 FitTextOutlinesToShapeOutlines( aOutlines2d
, aFWData
);
896 pRet
= CreateSdrObjectFromParagraphOutlines(
904 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */