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/compatflags.hxx>
23 #include <svx/svddef.hxx>
24 #include <svx/svdopath.hxx>
25 #include <vcl/kernarray.hxx>
26 #include <vcl/metric.hxx>
27 #include <svx/sdasitm.hxx>
28 #include <svx/sdtfsitm.hxx>
29 #include <vcl/virdev.hxx>
30 #include <svx/svditer.hxx>
31 #include <editeng/eeitem.hxx>
32 #include <editeng/frmdiritem.hxx>
33 #include <editeng/fontitem.hxx>
34 #include <editeng/postitem.hxx>
35 #include <editeng/wghtitem.hxx>
36 #include <editeng/fhgtitem.hxx>
37 #include <editeng/charscaleitem.hxx>
38 #include <svx/svdoashp.hxx>
39 #include <svx/sdshitm.hxx>
40 #include <svx/svdmodel.hxx>
41 #include <editeng/outlobj.hxx>
42 #include <editeng/editobj.hxx>
43 #include <o3tl/numeric.hxx>
47 #include <comphelper/processfactory.hxx>
48 #include <com/sun/star/i18n/BreakIterator.hpp>
49 #include <com/sun/star/i18n/ScriptType.hpp>
50 #include <basegfx/polygon/b2dpolypolygontools.hxx>
51 #include <basegfx/polygon/b2dpolygontools.hxx>
52 #include <sal/log.hxx>
53 #include <rtl/math.hxx>
54 #include <unotools/configmgr.hxx>
55 #include <comphelper/string.hxx>
57 using namespace com::sun::star
;
58 using namespace com::sun::star::uno
;
62 struct FWCharacterData
// representing a single character
64 std::vector
< tools::PolyPolygon
> vOutlines
;
65 tools::Rectangle aBoundRect
;
67 struct FWParagraphData
// representing a single paragraph
70 std::vector
< FWCharacterData
> vCharacters
;
71 tools::Rectangle aBoundRect
;
72 SvxFrameDirection nFrameDirection
;
74 struct FWTextArea
// representing multiple concluding paragraphs
76 std::vector
< FWParagraphData
> vParagraphs
;
77 tools::Rectangle aBoundRect
;
79 struct FWData
// representing the whole text
81 std::vector
< FWTextArea
> vTextAreas
;
82 double fHorizontalTextScaling
;
83 double fVerticalTextScaling
;
84 sal_uInt32 nMaxParagraphsPerTextArea
;
85 sal_Int32 nSingleLineHeight
;
92 static bool InitializeFontWorkData(
93 const SdrObjCustomShape
& rSdrObjCustomShape
,
94 const sal_uInt16 nOutlinesCount2d
,
98 bool bSingleLineMode
= false;
99 sal_uInt16 nTextAreaCount
= nOutlinesCount2d
;
100 if ( nOutlinesCount2d
& 1 )
101 bSingleLineMode
= true;
103 nTextAreaCount
>>= 1;
105 const SdrCustomShapeGeometryItem
& rGeometryItem( rSdrObjCustomShape
.GetMergedItem( SDRATTR_CUSTOMSHAPE_GEOMETRY
) );
106 const css::uno::Any
* pAny
= rGeometryItem
.GetPropertyValueByName( "TextPath", "ScaleX" );
108 *pAny
>>= rFWData
.bScaleX
;
110 rFWData
.bScaleX
= false;
112 if ( nTextAreaCount
)
114 rFWData
.bSingleLineMode
= bSingleLineMode
;
116 // setting the strings
117 OutlinerParaObject
* pParaObj(rSdrObjCustomShape
.GetOutlinerParaObject());
121 const EditTextObject
& rTextObj
= pParaObj
->GetTextObject();
122 sal_Int32 nParagraphsCount
= rTextObj
.GetParagraphCount();
124 // Collect all the lines from all paragraphs
125 std::vector
<int> aLineParaID
; // which para this line is in
126 std::vector
<int> aLineStart
; // where this line start in that para
127 std::vector
<int> aLineLength
;
128 std::vector
<OUString
> aParaText
;
129 for (sal_Int32 nPara
= 0; nPara
< nParagraphsCount
; ++nPara
)
131 aParaText
.push_back(rTextObj
.GetText(nPara
));
133 sal_Int32 nPrevPos
= 0;
136 // search line break.
137 if (!rSdrObjCustomShape
.getSdrModelFromSdrObject().GetCompatibilityFlag(
138 SdrCompatibilityFlag::LegacySingleLineFontwork
))
139 nPos
= aParaText
[nPara
].indexOf(sal_Unicode(u
'\1'), nPrevPos
);
141 nPos
= -1; // tdf#148000: ignore line breaks in legacy fontworks
143 aLineParaID
.push_back(nPara
);
144 aLineStart
.push_back(nPrevPos
);
145 aLineLength
.push_back((nPos
>= 0 ? nPos
: aParaText
[nPara
].getLength())
151 sal_Int32 nLinesLeft
= aLineParaID
.size();
153 rFWData
.nMaxParagraphsPerTextArea
= ((nLinesLeft
- 1) / nTextAreaCount
) + 1;
155 while (nLinesLeft
&& nTextAreaCount
)
157 FWTextArea aTextArea
;
158 sal_Int32 nLinesInPara
= ((nLinesLeft
- 1) / nTextAreaCount
) + 1;
159 for (sal_Int32 i
= 0; i
< nLinesInPara
; ++i
, ++nLine
)
161 FWParagraphData aParagraphData
;
162 aParagraphData
.aString
= aParaText
[aLineParaID
[nLine
]].subView(
163 aLineStart
[nLine
], aLineLength
[nLine
]);
165 // retrieving some paragraph attributes
166 const SfxItemSet
& rParaSet
= rTextObj
.GetParaAttribs(aLineParaID
[nLine
]);
167 aParagraphData
.nFrameDirection
= rParaSet
.Get(EE_PARA_WRITINGDIR
).GetValue();
168 aTextArea
.vParagraphs
.push_back(aParagraphData
);
170 rFWData
.vTextAreas
.push_back(aTextArea
);
171 nLinesLeft
-= nLinesInPara
;
181 static double GetLength( const tools::Polygon
& rPolygon
)
184 if ( rPolygon
.GetSize() > 1 )
186 sal_uInt16 nCount
= rPolygon
.GetSize();
188 fLength
+= rPolygon
.CalcDistance( nCount
, nCount
- 1 );
194 /* CalculateHorizontalScalingFactor returns the horizontal scaling factor for
195 the whole text object, so that each text will match its corresponding 2d Outline */
196 static void CalculateHorizontalScalingFactor(
197 const SdrObjCustomShape
& rSdrObjCustomShape
,
199 const tools::PolyPolygon
& rOutline2d
)
201 double fScalingFactor
= 1.0;
202 rFWData
.fVerticalTextScaling
= 1.0;
205 bool bSingleLineMode
= false;
206 sal_uInt16 nOutlinesCount2d
= rOutline2d
.Count();
209 const SvxFontItem
& rFontItem( rSdrObjCustomShape
.GetMergedItem( EE_CHAR_FONTINFO
) );
210 const SvxFontHeightItem
& rFontHeight( rSdrObjCustomShape
.GetMergedItem( EE_CHAR_FONTHEIGHT
) );
211 sal_Int32 nFontSize
= rFontHeight
.GetHeight();
214 aFont
.SetFontHeight( nFontSize
);
216 aFont
.SetFontHeight( rSdrObjCustomShape
.GetLogicRect().GetHeight() / rFWData
.nMaxParagraphsPerTextArea
);
218 aFont
.SetAlignment( ALIGN_TOP
);
219 aFont
.SetFamilyName( rFontItem
.GetFamilyName() );
220 aFont
.SetFamily( rFontItem
.GetFamily() );
221 aFont
.SetStyleName( rFontItem
.GetStyleName() );
222 const SvxPostureItem
& rPostureItem
= rSdrObjCustomShape
.GetMergedItem( EE_CHAR_ITALIC
);
223 aFont
.SetItalic( rPostureItem
.GetPosture() );
225 const SvxWeightItem
& rWeightItem
= rSdrObjCustomShape
.GetMergedItem( EE_CHAR_WEIGHT
);
226 aFont
.SetWeight( rWeightItem
.GetWeight() );
227 aFont
.SetOrientation( 0_deg10
);
228 // initializing virtual device
230 ScopedVclPtrInstance
< VirtualDevice
> pVirDev(DeviceFormat::WITHOUT_ALPHA
);
231 pVirDev
->SetMapMode(MapMode(MapUnit::Map100thMM
));
232 pVirDev
->SetFont( aFont
);
233 pVirDev
->SetAntialiasing( AntialiasingFlags::DisableText
);
235 if ( nOutlinesCount2d
& 1 )
236 bSingleLineMode
= true;
238 // In case of rFWData.bScaleX == true it loops with reduced font size until the current run
239 // results in a fScalingFactor >=1.0. The fact, that case rFWData.bScaleX == true keeps font
240 // size if possible, is not done here with scaling factor 1 but is done in method
241 // FitTextOutlinesToShapeOutlines()
245 bool bScalingFactorDefined
= false; // New calculation for each font size
246 for( const auto& rTextArea
: rFWData
.vTextAreas
)
248 // calculating the width of the corresponding 2d text area
249 double fWidth
= GetLength( rOutline2d
.GetObject( i
++ ) );
250 if ( !bSingleLineMode
)
252 fWidth
+= GetLength( rOutline2d
.GetObject( i
++ ) );
256 for( const auto& rParagraph
: rTextArea
.vParagraphs
)
258 double fTextWidth
= pVirDev
->GetTextWidth( rParagraph
.aString
);
259 if ( fTextWidth
> 0.0 )
261 double fScale
= fWidth
/ fTextWidth
;
262 if ( !bScalingFactorDefined
)
264 fScalingFactor
= fScale
;
265 bScalingFactorDefined
= true;
267 else if (fScale
< fScalingFactor
)
269 fScalingFactor
= fScale
;
275 if (fScalingFactor
< 1.0)
278 aFont
.SetFontHeight( nFontSize
);
279 pVirDev
->SetFont( aFont
);
282 while (rFWData
.bScaleX
&& fScalingFactor
< 1.0 && nFontSize
> 1 );
285 rFWData
.fVerticalTextScaling
= static_cast<double>(nFontSize
) / rFontHeight
.GetHeight();
287 rFWData
.fHorizontalTextScaling
= fScalingFactor
;
290 static void GetTextAreaOutline(
291 const FWData
& rFWData
,
292 const SdrObjCustomShape
& rSdrObjCustomShape
,
293 FWTextArea
& rTextArea
,
294 bool bSameLetterHeights
)
296 bool bIsVertical(rSdrObjCustomShape
.IsVerticalWriting());
297 sal_Int32 nVerticalOffset
= rFWData
.nMaxParagraphsPerTextArea
> rTextArea
.vParagraphs
.size()
298 ? rFWData
.nSingleLineHeight
/ 2 : 0;
300 for( auto& rParagraph
: rTextArea
.vParagraphs
)
302 const OUString
& rText
= rParagraph
.aString
;
303 if ( !rText
.isEmpty() )
305 // generating vcl/font
306 sal_uInt16 nScriptType
= i18n::ScriptType::LATIN
;
307 Reference
< i18n::XBreakIterator
> xBI( EnhancedCustomShapeFontWork::GetBreakIterator() );
310 nScriptType
= xBI
->getScriptType( rText
, 0 );
311 if( i18n::ScriptType::WEAK
== nScriptType
)
313 sal_Int32 nChg
= xBI
->endOfScript( rText
, 0, nScriptType
);
314 if (nChg
< rText
.getLength() && nChg
>= 0)
315 nScriptType
= xBI
->getScriptType( rText
, nChg
);
317 nScriptType
= i18n::ScriptType::LATIN
;
320 sal_uInt16 nFntItm
= EE_CHAR_FONTINFO
;
321 if ( nScriptType
== i18n::ScriptType::COMPLEX
)
322 nFntItm
= EE_CHAR_FONTINFO_CTL
;
323 else if ( nScriptType
== i18n::ScriptType::ASIAN
)
324 nFntItm
= EE_CHAR_FONTINFO_CJK
;
325 const SvxFontItem
& rFontItem
= static_cast<const SvxFontItem
&>(rSdrObjCustomShape
.GetMergedItem( nFntItm
));
328 aFont
.SetFontHeight( rFWData
.nSingleLineHeight
);
330 aFont
.SetAlignment( ALIGN_TOP
);
332 aFont
.SetFamilyName( rFontItem
.GetFamilyName() );
333 aFont
.SetFamily( rFontItem
.GetFamily() );
334 aFont
.SetStyleName( rFontItem
.GetStyleName() );
335 aFont
.SetOrientation( 0_deg10
);
337 const SvxPostureItem
& rPostureItem
= rSdrObjCustomShape
.GetMergedItem( EE_CHAR_ITALIC
);
338 aFont
.SetItalic( rPostureItem
.GetPosture() );
340 const SvxWeightItem
& rWeightItem
= rSdrObjCustomShape
.GetMergedItem( EE_CHAR_WEIGHT
);
341 aFont
.SetWeight( rWeightItem
.GetWeight() );
343 // initializing virtual device
344 ScopedVclPtrInstance
< VirtualDevice
> pVirDev(DeviceFormat::WITHOUT_ALPHA
);
345 pVirDev
->SetMapMode(MapMode(MapUnit::Map100thMM
));
346 pVirDev
->SetFont( aFont
);
347 pVirDev
->SetAntialiasing( AntialiasingFlags::DisableText
);
349 pVirDev
->EnableRTL();
350 if ( rParagraph
.nFrameDirection
== SvxFrameDirection::Horizontal_RL_TB
)
351 pVirDev
->SetLayoutMode( vcl::text::ComplexTextLayoutFlags::BiDiRtl
);
353 const SvxCharScaleWidthItem
& rCharScaleWidthItem
= rSdrObjCustomShape
.GetMergedItem( EE_CHAR_FONTWIDTH
);
354 sal_uInt16 nCharScaleWidth
= rCharScaleWidthItem
.GetValue();
355 sal_Int32 nWidth
= 0;
360 // vertical _> each single character needs to be rotated by 90
362 sal_Int32 nHeight
= 0;
363 tools::Rectangle aSingleCharacterUnion
;
364 for ( i
= 0; i
< rText
.getLength(); i
++ )
366 FWCharacterData aCharacterData
;
367 OUString
aCharText( rText
[ i
] );
368 if ( pVirDev
->GetTextOutlines( aCharacterData
.vOutlines
, aCharText
, 0, 0, -1, nWidth
, {} ) )
370 sal_Int32 nTextWidth
= pVirDev
->GetTextWidth( aCharText
);
371 if ( aCharacterData
.vOutlines
.empty() )
373 nHeight
+= rFWData
.nSingleLineHeight
;
377 for ( auto& rOutline
: aCharacterData
.vOutlines
)
380 rOutline
.Rotate( Point( nTextWidth
/ 2, rFWData
.nSingleLineHeight
/ 2 ), 900_deg10
);
381 aCharacterData
.aBoundRect
.Union( rOutline
.GetBoundRect() );
383 for ( auto& rOutline
: aCharacterData
.vOutlines
)
385 sal_Int32 nM
= - aCharacterData
.aBoundRect
.Left() + nHeight
;
386 rOutline
.Move( nM
, 0 );
387 aCharacterData
.aBoundRect
.Move( nM
, 0 );
389 nHeight
+= aCharacterData
.aBoundRect
.GetWidth() + ( rFWData
.nSingleLineHeight
/ 5 );
390 aSingleCharacterUnion
.Union( aCharacterData
.aBoundRect
);
393 rParagraph
.vCharacters
.push_back( aCharacterData
);
395 for ( auto& rCharacter
: rParagraph
.vCharacters
)
397 for ( auto& rOutline
: rCharacter
.vOutlines
)
399 rOutline
.Move( ( aSingleCharacterUnion
.GetWidth() - rCharacter
.aBoundRect
.GetWidth() ) / 2, 0 );
406 if ( ( nCharScaleWidth
!= 100 ) && nCharScaleWidth
)
407 { // applying character spacing
408 pVirDev
->GetTextArray( rText
, &aDXArry
);
409 FontMetric
aFontMetric( pVirDev
->GetFontMetric() );
410 aFont
.SetAverageFontWidth( static_cast<sal_Int32
>( static_cast<double>(aFontMetric
.GetAverageFontWidth()) * ( double(100) / static_cast<double>(nCharScaleWidth
) ) ) );
411 pVirDev
->SetFont( aFont
);
413 FWCharacterData aCharacterData
;
414 if ( pVirDev
->GetTextOutlines( aCharacterData
.vOutlines
, rText
, 0, 0, -1, nWidth
, aDXArry
) )
416 rParagraph
.vCharacters
.push_back( aCharacterData
);
420 // GetTextOutlines failed what usually means that it is
421 // not implemented. To make FontWork not fail (it is
422 // dependent of graphic content to get a Range) create
423 // a rectangle substitution for now
424 pVirDev
->GetTextArray( rText
, &aDXArry
);
425 aCharacterData
.vOutlines
.clear();
429 for(size_t a(0); a
< aDXArry
.size(); a
++)
431 const basegfx::B2DPolygon
aPolygon(
432 basegfx::utils::createPolygonFromRect(
434 0 == a
? 0 : aDXArry
[a
- 1],
437 aFont
.GetFontHeight()
439 aCharacterData
.vOutlines
.push_back(tools::PolyPolygon(tools::Polygon(aPolygon
)));
444 const basegfx::B2DPolygon
aPolygon(
445 basegfx::utils::createPolygonFromRect(
449 aDXArry
.empty() ? 10 : aDXArry
.back(),
450 aFont
.GetFontHeight()
452 aCharacterData
.vOutlines
.push_back(tools::PolyPolygon(tools::Polygon(aPolygon
)));
456 rParagraph
.vCharacters
.push_back( aCharacterData
);
460 // vertical alignment
461 for ( auto& rCharacter
: rParagraph
.vCharacters
)
463 for( tools::PolyPolygon
& rPolyPoly
: rCharacter
.vOutlines
)
465 if ( nVerticalOffset
)
466 rPolyPoly
.Move( 0, nVerticalOffset
);
468 // retrieving the boundrect for the paragraph
469 tools::Rectangle
aBoundRect( rPolyPoly
.GetBoundRect() );
470 rParagraph
.aBoundRect
.Union( aBoundRect
);
474 // updating the boundrect for the text area by merging the current paragraph boundrect
475 if ( rParagraph
.aBoundRect
.IsEmpty() )
477 if ( rTextArea
.aBoundRect
.IsEmpty() )
478 rTextArea
.aBoundRect
= tools::Rectangle( Point( 0, 0 ), Size( 1, rFWData
.nSingleLineHeight
) );
480 rTextArea
.aBoundRect
.AdjustBottom(rFWData
.nSingleLineHeight
);
484 tools::Rectangle
& rParagraphBoundRect
= rParagraph
.aBoundRect
;
485 rTextArea
.aBoundRect
.Union( rParagraphBoundRect
);
487 if ( bSameLetterHeights
)
489 for ( auto& rCharacter
: rParagraph
.vCharacters
)
491 for( auto& rOutline
: rCharacter
.vOutlines
)
493 tools::Rectangle
aPolyPolyBoundRect( rOutline
.GetBoundRect() );
494 if (aPolyPolyBoundRect
.GetHeight() != rParagraphBoundRect
.GetHeight() && aPolyPolyBoundRect
.GetHeight())
495 rOutline
.Scale( 1.0, static_cast<double>(rParagraphBoundRect
.GetHeight()) / aPolyPolyBoundRect
.GetHeight() );
496 aPolyPolyBoundRect
= rOutline
.GetBoundRect();
497 sal_Int32 nMove
= aPolyPolyBoundRect
.Top() - rParagraphBoundRect
.Top();
499 rOutline
.Move( 0, -nMove
);
505 nVerticalOffset
-= rFWData
.nSingleLineHeight
;
507 nVerticalOffset
+= rFWData
.nSingleLineHeight
;
511 static bool GetFontWorkOutline(
513 const SdrObjCustomShape
& rSdrObjCustomShape
)
515 SdrTextHorzAdjust
eHorzAdjust(rSdrObjCustomShape
.GetMergedItem( SDRATTR_TEXT_HORZADJUST
).GetValue());
516 drawing::TextFitToSizeType
const eFTS(rSdrObjCustomShape
.GetMergedItem( SDRATTR_TEXT_FITTOSIZE
).GetValue());
518 bool bSameLetterHeights
= false;
519 const SdrCustomShapeGeometryItem
& rGeometryItem(rSdrObjCustomShape
.GetMergedItem( SDRATTR_CUSTOMSHAPE_GEOMETRY
));
520 const css::uno::Any
* pAny
= rGeometryItem
.GetPropertyValueByName( "TextPath", "SameLetterHeights" );
522 *pAny
>>= bSameLetterHeights
;
524 const SvxFontHeightItem
& rFontHeight( rSdrObjCustomShape
.GetMergedItem( EE_CHAR_FONTHEIGHT
) );
526 rFWData
.nSingleLineHeight
= rFWData
.fVerticalTextScaling
* rFontHeight
.GetHeight();
528 rFWData
.nSingleLineHeight
= static_cast<sal_Int32
>( ( static_cast<double>( rSdrObjCustomShape
.GetLogicRect().GetHeight() )
529 / rFWData
.nMaxParagraphsPerTextArea
) * rFWData
.fHorizontalTextScaling
);
531 if (rFWData
.nSingleLineHeight
== SAL_MIN_INT32
)
534 for ( auto& rTextArea
: rFWData
.vTextAreas
)
542 if (eFTS
== drawing::TextFitToSizeType_ALLLINES
||
543 // tdf#97630 interpret PROPORTIONAL same as ALLLINES so we don't
544 // need another ODF attribute!
545 eFTS
== drawing::TextFitToSizeType_PROPORTIONAL
)
547 for ( auto& rParagraph
: rTextArea
.vParagraphs
)
549 sal_Int32 nParaWidth
= rParagraph
.aBoundRect
.GetWidth();
552 double fScale
= static_cast<double>(rTextArea
.aBoundRect
.GetWidth()) / nParaWidth
;
554 for ( auto& rCharacter
: rParagraph
.vCharacters
)
556 for( auto& rOutline
: rCharacter
.vOutlines
)
558 rOutline
.Scale( fScale
, 1.0 );
564 else if (rFWData
.bScaleX
)
566 const SdrTextVertAdjust nVertJustify
= rSdrObjCustomShape
.GetMergedItem( SDRATTR_TEXT_VERTADJUST
).GetValue();
567 double fFactor
= nVertJustify
== SdrTextVertAdjust::SDRTEXTVERTADJUST_BOTTOM
? -0.5 : ( nVertJustify
== SdrTextVertAdjust::SDRTEXTVERTADJUST_TOP
? 0.5 : 0 );
569 for ( auto& rParagraph
: rTextArea
.vParagraphs
)
571 sal_Int32 nHorzDiff
= 0;
572 sal_Int32 nVertDiff
= static_cast<double>( rFWData
.nSingleLineHeight
) * fFactor
* ( rTextArea
.vParagraphs
.size() - 1 );
574 if ( eHorzAdjust
== SDRTEXTHORZADJUST_CENTER
)
575 nHorzDiff
= ( rFWData
.fHorizontalTextScaling
* rTextArea
.aBoundRect
.GetWidth() - rParagraph
.aBoundRect
.GetWidth() ) / 2;
576 else if ( eHorzAdjust
== SDRTEXTHORZADJUST_RIGHT
)
577 nHorzDiff
= ( rFWData
.fHorizontalTextScaling
* rTextArea
.aBoundRect
.GetWidth() - rParagraph
.aBoundRect
.GetWidth() );
579 if (nHorzDiff
|| nVertDiff
)
581 for ( auto& rCharacter
: rParagraph
.vCharacters
)
583 for( auto& rOutline
: rCharacter
.vOutlines
)
585 rOutline
.Move( nHorzDiff
, nVertDiff
);
593 switch( eHorzAdjust
)
595 case SDRTEXTHORZADJUST_RIGHT
:
596 case SDRTEXTHORZADJUST_CENTER
:
598 for ( auto& rParagraph
: rTextArea
.vParagraphs
)
600 sal_Int32 nHorzDiff
= 0;
601 if ( eHorzAdjust
== SDRTEXTHORZADJUST_CENTER
)
602 nHorzDiff
= ( rTextArea
.aBoundRect
.GetWidth() - rParagraph
.aBoundRect
.GetWidth() ) / 2;
603 else if ( eHorzAdjust
== SDRTEXTHORZADJUST_RIGHT
)
604 nHorzDiff
= ( rTextArea
.aBoundRect
.GetWidth() - rParagraph
.aBoundRect
.GetWidth() );
607 for ( auto& rCharacter
: rParagraph
.vCharacters
)
609 for( auto& rOutline
: rCharacter
.vOutlines
)
611 rOutline
.Move( nHorzDiff
, 0 );
619 case SDRTEXTHORZADJUST_BLOCK
: break; // don't know
620 case SDRTEXTHORZADJUST_LEFT
: break; // already left aligned -> nothing to do
628 static basegfx::B2DPolyPolygon
GetOutlinesFromShape2d( const SdrObject
* pShape2d
)
630 basegfx::B2DPolyPolygon aOutlines2d
;
632 SdrObjListIter
aObjListIter( *pShape2d
, SdrIterMode::DeepWithGroups
);
633 while( aObjListIter
.IsMore() )
635 SdrObject
* pPartObj
= aObjListIter
.Next();
636 if ( auto pPathObj
= dynamic_cast<const SdrPathObj
*>( pPartObj
))
638 basegfx::B2DPolyPolygon
aCandidate(pPathObj
->GetPathPoly());
639 if(aCandidate
.areControlPointsUsed())
641 aCandidate
= basegfx::utils::adaptiveSubdivideByAngle(aCandidate
);
643 aOutlines2d
.append(aCandidate
);
650 static void CalcDistances( const tools::Polygon
& rPoly
, std::vector
< double >& rDistances
)
652 sal_uInt16 i
, nCount
= rPoly
.GetSize();
656 for ( i
= 0; i
< nCount
; i
++ )
658 double fDistance
= i
? rPoly
.CalcDistance( i
, i
- 1 ) : 0.0;
659 rDistances
.push_back( fDistance
);
661 std::partial_sum( rDistances
.begin(), rDistances
.end(), rDistances
.begin() );
662 double fLength
= rDistances
[ rDistances
.size() - 1 ];
665 for ( auto& rDistance
: rDistances
)
666 rDistance
/= fLength
;
670 static void InsertMissingOutlinePoints( const std::vector
< double >& rDistances
,
671 const tools::Rectangle
& rTextAreaBoundRect
, tools::Polygon
& rPoly
)
673 sal_uInt16 nSize
= rPoly
.GetSize();
677 tools::Long nTextWidth
= rTextAreaBoundRect
.GetWidth();
680 throw o3tl::divide_by_zero();
682 double fLastDistance
= 0.0;
683 for (sal_uInt16 i
= 0; i
< nSize
; ++i
)
685 Point
& rPoint
= rPoly
[ i
];
686 double fDistance
= static_cast<double>( rPoint
.X() - rTextAreaBoundRect
.Left() ) / static_cast<double>(nTextWidth
);
689 if ( fDistance
> fLastDistance
)
691 std::vector
< double >::const_iterator aIter
= std::upper_bound( rDistances
.begin(), rDistances
.end(), fLastDistance
);
692 if ( aIter
!= rDistances
.end() && ( *aIter
> fLastDistance
) && ( *aIter
< fDistance
) )
694 Point
& rPt0
= rPoly
[ i
- 1 ];
695 sal_Int32 fX
= rPoint
.X() - rPt0
.X();
696 sal_Int32 fY
= rPoint
.Y() - rPt0
.Y();
697 double fd
= ( 1.0 / ( fDistance
- fLastDistance
) ) * ( *aIter
- fLastDistance
);
698 rPoly
.Insert( i
, Point( static_cast<sal_Int32
>( rPt0
.X() + fX
* fd
), static_cast<sal_Int32
>( rPt0
.Y() + fY
* fd
) ) );
702 else if ( fDistance
< fLastDistance
)
704 std::vector
< double >::const_iterator aIter
= std::lower_bound( rDistances
.begin(), rDistances
.end(), fLastDistance
);
705 if ( aIter
!= rDistances
.begin() )
708 if ( ( *aIter
> fDistance
) && ( *aIter
< fLastDistance
) )
710 Point
& rPt0
= rPoly
[ i
- 1 ];
711 sal_Int32 fX
= rPoint
.X() - rPt0
.X();
712 sal_Int32 fY
= rPoint
.Y() - rPt0
.Y();
713 double fd
= ( 1.0 / ( fDistance
- fLastDistance
) ) * ( *aIter
- fLastDistance
);
714 rPoly
.Insert( i
, Point( static_cast<sal_Int32
>( rPt0
.X() + fX
* fd
), static_cast<sal_Int32
>( rPt0
.Y() + fY
* fd
) ) );
720 fLastDistance
= fDistance
;
724 static void GetPoint( const tools::Polygon
& rPoly
, const std::vector
< double >& rDistances
, const double& fX
, double& fx1
, double& fy1
)
727 if ( rPoly
.GetSize() <= 1 )
730 std::vector
< double >::const_iterator aIter
= std::lower_bound( rDistances
.begin(), rDistances
.end(), fX
);
731 sal_uInt16 nIdx
= sal::static_int_cast
<sal_uInt16
>( std::distance( rDistances
.begin(), aIter
) );
732 if ( aIter
== rDistances
.end() )
734 const Point
& rPt
= rPoly
[ nIdx
];
737 if ( !nIdx
|| ( aIter
== rDistances
.end() ) || rtl::math::approxEqual( *aIter
, fX
) )
740 nIdx
= sal::static_int_cast
<sal_uInt16
>( std::distance( rDistances
.begin(), aIter
) );
741 double fDist0
= *( aIter
- 1 );
742 double fd
= ( 1.0 / ( *aIter
- fDist0
) ) * ( fX
- fDist0
);
743 const Point
& rPt2
= rPoly
[ nIdx
- 1 ];
744 double fWidth
= rPt
.X() - rPt2
.X();
745 double fHeight
= rPt
.Y() - rPt2
.Y();
748 fx1
= rPt2
.X() + fWidth
;
749 fy1
= rPt2
.Y() + fHeight
;
752 static void FitTextOutlinesToShapeOutlines( const tools::PolyPolygon
& aOutlines2d
, FWData
& rFWData
)
754 sal_uInt16 nOutline2dIdx
= 0;
755 for( auto& rTextArea
: rFWData
.vTextAreas
)
757 tools::Rectangle rTextAreaBoundRect
= rTextArea
.aBoundRect
;
758 sal_Int32 nLeft
= rTextAreaBoundRect
.Left();
759 sal_Int32 nTop
= rTextAreaBoundRect
.Top();
760 sal_Int32 nWidth
= rTextAreaBoundRect
.GetWidth();
761 sal_Int32 nHeight
= rTextAreaBoundRect
.GetHeight();
765 nWidth
*= rFWData
.fHorizontalTextScaling
;
768 if ( rFWData
.bSingleLineMode
&& nHeight
&& nWidth
)
770 if ( nOutline2dIdx
>= aOutlines2d
.Count() )
772 const tools::Polygon
& rOutlinePoly( aOutlines2d
[ nOutline2dIdx
++ ] );
773 const sal_uInt16 nPointCount
= rOutlinePoly
.GetSize();
774 if ( nPointCount
> 1 )
776 std::vector
< double > vDistances
;
777 vDistances
.reserve( nPointCount
);
778 CalcDistances( rOutlinePoly
, vDistances
);
779 if ( !vDistances
.empty() )
781 for( auto& rParagraph
: rTextArea
.vParagraphs
)
783 for ( auto& rCharacter
: rParagraph
.vCharacters
)
785 for( tools::PolyPolygon
& rPolyPoly
: rCharacter
.vOutlines
)
787 tools::Rectangle
aBoundRect( rPolyPoly
.GetBoundRect() );
788 double fx1
= aBoundRect
.Left() - nLeft
;
789 double fx2
= aBoundRect
.Right() - nLeft
;
791 double fM1
= fx1
/ static_cast<double>(nWidth
);
792 double fM2
= fx2
/ static_cast<double>(nWidth
);
794 GetPoint( rOutlinePoly
, vDistances
, fM1
, fx1
, fy1
);
795 GetPoint( rOutlinePoly
, vDistances
, fM2
, fx2
, fy2
);
797 double fvx
= fy2
- fy1
;
798 double fvy
= - ( fx2
- fx1
);
799 fx1
= fx1
+ ( ( fx2
- fx1
) * 0.5 );
800 fy1
= fy1
+ ( ( fy2
- fy1
) * 0.5 );
802 double fAngle
= atan2( -fvx
, -fvy
);
803 double fL
= hypot( fvx
, fvy
);
806 SAL_WARN("svx", "FitTextOutlinesToShapeOutlines div-by-zero, abandon fit");
811 fL
= rTextArea
.aBoundRect
.GetHeight() / 2.0 + rTextArea
.aBoundRect
.Top() - rParagraph
.aBoundRect
.Center().Y();
814 rPolyPoly
.Rotate( Point( aBoundRect
.Center().X(), rParagraph
.aBoundRect
.Center().Y() ), sin( fAngle
), cos( fAngle
) );
815 rPolyPoly
.Move( static_cast<sal_Int32
>( ( fx1
+ fvx
)- aBoundRect
.Center().X() ), static_cast<sal_Int32
>( ( fy1
+ fvy
) - rParagraph
.aBoundRect
.Center().Y() ) );
824 if ( ( nOutline2dIdx
+ 1 ) >= aOutlines2d
.Count() )
826 const tools::Polygon
& rOutlinePoly( aOutlines2d
[ nOutline2dIdx
++ ] );
827 const tools::Polygon
& rOutlinePoly2( aOutlines2d
[ nOutline2dIdx
++ ] );
828 const sal_uInt16 nPointCount
= rOutlinePoly
.GetSize();
829 const sal_uInt16 nPointCount2
= rOutlinePoly2
.GetSize();
830 if ( ( nPointCount
> 1 ) && ( nPointCount2
> 1 ) )
832 std::vector
< double > vDistances
;
833 vDistances
.reserve( nPointCount
);
834 std::vector
< double > vDistances2
;
835 vDistances2
.reserve( nPointCount2
);
836 CalcDistances( rOutlinePoly
, vDistances
);
837 CalcDistances( rOutlinePoly2
, vDistances2
);
838 for( auto& rParagraph
: rTextArea
.vParagraphs
)
840 for ( auto& rCharacter
: rParagraph
.vCharacters
)
842 for( tools::PolyPolygon
& rPolyPoly
: rCharacter
.vOutlines
)
844 sal_uInt16 i
, nPolyCount
= rPolyPoly
.Count();
845 for ( i
= 0; i
< nPolyCount
; i
++ )
848 basegfx::B2DPolygon
aCandidate(rPolyPoly
[ i
].getB2DPolygon());
850 if(aCandidate
.areControlPointsUsed())
852 aCandidate
= basegfx::utils::adaptiveSubdivideByAngle(aCandidate
);
855 // create local polygon copy to work on
856 tools::Polygon
aLocalPoly(aCandidate
);
858 InsertMissingOutlinePoints( vDistances
, rTextAreaBoundRect
, aLocalPoly
);
859 InsertMissingOutlinePoints( vDistances2
, rTextAreaBoundRect
, aLocalPoly
);
861 sal_uInt16 _nPointCount
= aLocalPoly
.GetSize();
864 if (!nWidth
|| !nHeight
)
865 throw o3tl::divide_by_zero();
866 for (sal_uInt16 j
= 0; j
< _nPointCount
; ++j
)
868 Point
& rPoint
= aLocalPoly
[ j
];
869 rPoint
.AdjustX( -nLeft
);
870 rPoint
.AdjustY( -nTop
);
871 double fX
= static_cast<double>(rPoint
.X()) / static_cast<double>(nWidth
);
872 double fY
= static_cast<double>(rPoint
.Y()) / static_cast<double>(nHeight
);
874 double fx1
, fy1
, fx2
, fy2
;
875 GetPoint( rOutlinePoly
, vDistances
, fX
, fx1
, fy1
);
876 GetPoint( rOutlinePoly2
, vDistances2
, fX
, fx2
, fy2
);
877 double fWidth
= fx2
- fx1
;
878 double fHeight
= fy2
- fy1
;
879 rPoint
.setX( static_cast<sal_Int32
>( fx1
+ fWidth
* fY
) );
880 rPoint
.setY( static_cast<sal_Int32
>( fy1
+ fHeight
* fY
) );
884 // write back polygon
885 rPolyPoly
[ i
] = aLocalPoly
;
895 static rtl::Reference
<SdrObject
> CreateSdrObjectFromParagraphOutlines(
896 const FWData
& rFWData
,
897 const SdrObjCustomShape
& rSdrObjCustomShape
)
899 rtl::Reference
<SdrObject
> pRet
;
900 basegfx::B2DPolyPolygon aPolyPoly
;
901 if ( !rFWData
.vTextAreas
.empty() )
903 for ( const auto& rTextArea
: rFWData
.vTextAreas
)
905 for ( const auto& rParagraph
: rTextArea
.vParagraphs
)
907 for ( const auto& rCharacter
: rParagraph
.vCharacters
)
909 for( const auto& rOutline
: rCharacter
.vOutlines
)
911 aPolyPoly
.append( rOutline
.getB2DPolyPolygon() );
917 pRet
= new SdrPathObj(
918 rSdrObjCustomShape
.getSdrModelFromSdrObject(),
920 std::move(aPolyPoly
));
922 SfxItemSet
aSet(rSdrObjCustomShape
.GetMergedItemSet());
923 aSet
.ClearItem( SDRATTR_TEXTDIRECTION
); //SJ: vertical writing is not required, by removing this item no outliner is created
924 aSet
.Put(makeSdrShadowItem(false)); // #i37011# NO shadow for FontWork geometry
925 pRet
->SetMergedItemSet( aSet
); // * otherwise we would crash, because the outliner tries to create a Paraobject, but there is no model
931 Reference
< i18n::XBreakIterator
> EnhancedCustomShapeFontWork::mxBreakIterator
;
933 Reference
< i18n::XBreakIterator
> const & EnhancedCustomShapeFontWork::GetBreakIterator()
935 if ( !mxBreakIterator
.is() )
937 Reference
< uno::XComponentContext
> xContext
= ::comphelper::getProcessComponentContext();
938 mxBreakIterator
= i18n::BreakIterator::create(xContext
);
940 return mxBreakIterator
;
943 rtl::Reference
<SdrObject
> EnhancedCustomShapeFontWork::CreateFontWork(
944 const SdrObject
* pShape2d
,
945 const SdrObjCustomShape
& rSdrObjCustomShape
)
947 rtl::Reference
<SdrObject
> pRet
;
949 // calculating scaling factor is too slow
950 if (utl::ConfigManager::IsFuzzing())
953 tools::PolyPolygon
aOutlines2d( GetOutlinesFromShape2d( pShape2d
) );
954 sal_uInt16 nOutlinesCount2d
= aOutlines2d
.Count();
955 if ( nOutlinesCount2d
)
959 if(InitializeFontWorkData(rSdrObjCustomShape
, nOutlinesCount2d
, aFWData
))
961 /* retrieves the horizontal scaling factor that has to be used
962 to fit each paragraph text into its corresponding 2d outline */
963 CalculateHorizontalScalingFactor(
968 /* retrieving the Outlines for the each Paragraph. */
969 if(!GetFontWorkOutline(
976 FitTextOutlinesToShapeOutlines( aOutlines2d
, aFWData
);
978 pRet
= CreateSdrObjectFromParagraphOutlines(
986 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */