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 <comphelper/configuration.hxx>
56 using namespace com::sun::star
;
57 using namespace com::sun::star::uno
;
61 struct FWCharacterData
// representing a single character
63 std::vector
< tools::PolyPolygon
> vOutlines
;
64 tools::Rectangle aBoundRect
;
66 struct FWParagraphData
// representing a single paragraph
69 std::vector
< FWCharacterData
> vCharacters
;
70 tools::Rectangle aBoundRect
;
71 SvxFrameDirection nFrameDirection
;
73 struct FWTextArea
// representing multiple concluding paragraphs
75 std::vector
< FWParagraphData
> vParagraphs
;
76 tools::Rectangle aBoundRect
;
77 sal_Int32 nHAlignMove
= 0;
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( u
"TextPath"_ustr
, u
"ScaleX"_ustr
);
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::LegacyFontwork
))
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
, &aDXArray
);
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
, aDXArray
) )
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
, &aDXArray
);
425 aCharacterData
.vOutlines
.clear();
427 if(!aDXArray
.empty())
429 for(size_t a(0); a
< aDXArray
.size(); a
++)
431 const basegfx::B2DPolygon
aPolygon(
432 basegfx::utils::createPolygonFromRect(
434 0 == a
? 0 : aDXArray
[a
- 1],
437 aFont
.GetFontHeight()
439 aCharacterData
.vOutlines
.emplace_back(tools::Polygon(aPolygon
));
444 const basegfx::B2DPolygon
aPolygon(
445 basegfx::utils::createPolygonFromRect(
450 aFont
.GetFontHeight()
452 aCharacterData
.vOutlines
.emplace_back(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( u
"TextPath"_ustr
, u
"SameLetterHeights"_ustr
);
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 );
573 rTextArea
.nHAlignMove
= nVertDiff
;
575 if ( eHorzAdjust
== SDRTEXTHORZADJUST_CENTER
)
576 nHorzDiff
= ( rFWData
.fHorizontalTextScaling
* rTextArea
.aBoundRect
.GetWidth() - rParagraph
.aBoundRect
.GetWidth() ) / 2;
577 else if ( eHorzAdjust
== SDRTEXTHORZADJUST_RIGHT
)
578 nHorzDiff
= ( rFWData
.fHorizontalTextScaling
* rTextArea
.aBoundRect
.GetWidth() - rParagraph
.aBoundRect
.GetWidth() );
580 if (nHorzDiff
|| nVertDiff
)
582 for ( auto& rCharacter
: rParagraph
.vCharacters
)
584 for( auto& rOutline
: rCharacter
.vOutlines
)
586 rOutline
.Move( nHorzDiff
, nVertDiff
);
594 switch( eHorzAdjust
)
596 case SDRTEXTHORZADJUST_RIGHT
:
597 case SDRTEXTHORZADJUST_CENTER
:
599 for ( auto& rParagraph
: rTextArea
.vParagraphs
)
601 sal_Int32 nHorzDiff
= 0;
602 if ( eHorzAdjust
== SDRTEXTHORZADJUST_CENTER
)
603 nHorzDiff
= ( rTextArea
.aBoundRect
.GetWidth() - rParagraph
.aBoundRect
.GetWidth() ) / 2;
604 else if ( eHorzAdjust
== SDRTEXTHORZADJUST_RIGHT
)
605 nHorzDiff
= ( rTextArea
.aBoundRect
.GetWidth() - rParagraph
.aBoundRect
.GetWidth() );
608 for ( auto& rCharacter
: rParagraph
.vCharacters
)
610 for( auto& rOutline
: rCharacter
.vOutlines
)
612 rOutline
.Move( nHorzDiff
, 0 );
620 case SDRTEXTHORZADJUST_BLOCK
: break; // don't know
621 case SDRTEXTHORZADJUST_LEFT
: break; // already left aligned -> nothing to do
629 static basegfx::B2DPolyPolygon
GetOutlinesFromShape2d( const SdrObject
* pShape2d
)
631 basegfx::B2DPolyPolygon aOutlines2d
;
633 SdrObjListIter
aObjListIter( *pShape2d
, SdrIterMode::DeepWithGroups
);
634 while( aObjListIter
.IsMore() )
636 SdrObject
* pPartObj
= aObjListIter
.Next();
637 if ( auto pPathObj
= dynamic_cast<const SdrPathObj
*>( pPartObj
))
639 basegfx::B2DPolyPolygon
aCandidate(pPathObj
->GetPathPoly());
640 if(aCandidate
.areControlPointsUsed())
642 aCandidate
= basegfx::utils::adaptiveSubdivideByAngle(aCandidate
);
644 aOutlines2d
.append(aCandidate
);
651 static void CalcDistances( const tools::Polygon
& rPoly
, std::vector
< double >& rDistances
)
653 sal_uInt16 i
, nCount
= rPoly
.GetSize();
657 for ( i
= 0; i
< nCount
; i
++ )
659 double fDistance
= i
? rPoly
.CalcDistance( i
, i
- 1 ) : 0.0;
660 rDistances
.push_back( fDistance
);
662 std::partial_sum( rDistances
.begin(), rDistances
.end(), rDistances
.begin() );
663 double fLength
= rDistances
[ rDistances
.size() - 1 ];
666 for ( auto& rDistance
: rDistances
)
667 rDistance
/= fLength
;
671 static void InsertMissingOutlinePoints( const std::vector
< double >& rDistances
,
672 const tools::Rectangle
& rTextAreaBoundRect
, tools::Polygon
& rPoly
)
674 sal_uInt16 nSize
= rPoly
.GetSize();
678 tools::Long nTextWidth
= rTextAreaBoundRect
.GetWidth();
681 throw o3tl::divide_by_zero();
683 double fLastDistance
= 0.0;
684 for (sal_uInt16 i
= 0; i
< nSize
; ++i
)
686 Point
& rPoint
= rPoly
[ i
];
687 double fDistance
= static_cast<double>( rPoint
.X() - rTextAreaBoundRect
.Left() ) / static_cast<double>(nTextWidth
);
690 if ( fDistance
> fLastDistance
)
692 std::vector
< double >::const_iterator aIter
= std::upper_bound( rDistances
.begin(), rDistances
.end(), fLastDistance
);
693 if ( aIter
!= rDistances
.end() && ( *aIter
> fLastDistance
) && ( *aIter
< fDistance
) )
695 Point
& rPt0
= rPoly
[ i
- 1 ];
696 sal_Int32 fX
= rPoint
.X() - rPt0
.X();
697 sal_Int32 fY
= rPoint
.Y() - rPt0
.Y();
698 double fd
= ( 1.0 / ( fDistance
- fLastDistance
) ) * ( *aIter
- fLastDistance
);
699 rPoly
.Insert( i
, Point( static_cast<sal_Int32
>( rPt0
.X() + fX
* fd
), static_cast<sal_Int32
>( rPt0
.Y() + fY
* fd
) ) );
703 else if ( fDistance
< fLastDistance
)
705 std::vector
< double >::const_iterator aIter
= std::lower_bound( rDistances
.begin(), rDistances
.end(), fLastDistance
);
706 if ( aIter
!= rDistances
.begin() )
709 if ( ( *aIter
> fDistance
) && ( *aIter
< fLastDistance
) )
711 Point
& rPt0
= rPoly
[ i
- 1 ];
712 sal_Int32 fX
= rPoint
.X() - rPt0
.X();
713 sal_Int32 fY
= rPoint
.Y() - rPt0
.Y();
714 double fd
= ( 1.0 / ( fDistance
- fLastDistance
) ) * ( *aIter
- fLastDistance
);
715 rPoly
.Insert( i
, Point( static_cast<sal_Int32
>( rPt0
.X() + fX
* fd
), static_cast<sal_Int32
>( rPt0
.Y() + fY
* fd
) ) );
721 fLastDistance
= fDistance
;
725 //only 2 types used: 'const tools::Polygon&' and 'const std::vector<Point>&'
727 static void GetPoint( T rPoly
, const std::vector
< double >& rDistances
, const double& fX
, double& fx1
, double& fy1
)
730 if (rPoly
.size() <= 1)
733 std::vector
< double >::const_iterator aIter
= std::lower_bound( rDistances
.begin(), rDistances
.end(), fX
);
734 sal_uInt16 nIdx
= sal::static_int_cast
<sal_uInt16
>( std::distance( rDistances
.begin(), aIter
) );
735 if ( aIter
== rDistances
.end() )
737 const Point
& rPt
= rPoly
[ nIdx
];
740 if ( !nIdx
|| ( aIter
== rDistances
.end() ) || rtl::math::approxEqual( *aIter
, fX
) )
743 nIdx
= sal::static_int_cast
<sal_uInt16
>( std::distance( rDistances
.begin(), aIter
) );
744 double fDist0
= *( aIter
- 1 );
745 double fd
= ( 1.0 / ( *aIter
- fDist0
) ) * ( fX
- fDist0
);
746 const Point
& rPt2
= rPoly
[ nIdx
- 1 ];
747 double fWidth
= rPt
.X() - rPt2
.X();
748 double fHeight
= rPt
.Y() - rPt2
.Y();
751 fx1
= rPt2
.X() + fWidth
;
752 fy1
= rPt2
.Y() + fHeight
;
755 static void FitTextOutlinesToShapeOutlines(const tools::PolyPolygon
& aOutlines2d
, FWData
& rFWData
,
756 SdrTextHorzAdjust eHorzAdjust
, bool bPPFontwork
)
758 sal_uInt16 nOutline2dIdx
= 0;
759 for( auto& rTextArea
: rFWData
.vTextAreas
)
761 tools::Rectangle rTextAreaBoundRect
= rTextArea
.aBoundRect
;
762 sal_Int32 nLeft
= rTextAreaBoundRect
.Left();
763 sal_Int32 nTop
= rTextAreaBoundRect
.Top();
764 sal_Int32 nWidth
= rTextAreaBoundRect
.GetWidth();
765 sal_Int32 nHeight
= rTextAreaBoundRect
.GetHeight();
769 nWidth
*= rFWData
.fHorizontalTextScaling
;
772 if ( rFWData
.bSingleLineMode
&& nHeight
&& nWidth
)
774 if ( nOutline2dIdx
>= aOutlines2d
.Count() )
776 const tools::Polygon
& rOutlinePoly( aOutlines2d
[ nOutline2dIdx
++ ] );
777 const sal_uInt16 nPointCount
= rOutlinePoly
.GetSize();
778 if ( nPointCount
> 1 )
780 std::vector
< double > vDistances
;
781 vDistances
.reserve( nPointCount
);
782 CalcDistances( rOutlinePoly
, vDistances
);
784 if ( !vDistances
.empty() )
786 // horizontal alignment: how much we have to move text to the right.
790 case SDRTEXTHORZADJUST_RIGHT
:
791 nAdjust
= 2; // 2 half of the possible
793 case SDRTEXTHORZADJUST_CENTER
:
794 nAdjust
= 1; // 1 half of the possible
796 case SDRTEXTHORZADJUST_BLOCK
:
797 nAdjust
= -1; // don't know what it is, so don't even align
799 case SDRTEXTHORZADJUST_LEFT
:
800 nAdjust
= 0; // no need to move
804 if (bPPFontwork
&& rTextArea
.vParagraphs
.size() > 1 && nAdjust
>= 0)
806 // If we have multiple lines of text to fit to the outline (curve)
807 // then we have to be able to calculate outer versions of the outline
808 // where we can fit the next lines of texts
809 // those outer lines will be wider (or shorter) as the original outline
810 // and probably will looks different as the original outline.
812 // for example if we have an outline like this:
814 // then the middle part will have the same normals, so distances there,
815 // will not change for an outer outline
816 // while the points near the edge will have different normals,
817 // distances around there will increase for an outer (wider) outline
819 //Normal vectors for every rOutlinePoly point. 1024 long
820 std::vector
<Point
> vNorm
;
821 //wider curve path points, for current paragraph (rOutlinePoly + vNorm*line)
822 std::vector
<Point
> vCurOutline
;
823 //distances between points of this wider curve
824 std::vector
<double> vCurDistances
;
826 vCurDistances
.reserve(nPointCount
);
827 vCurOutline
.reserve(nPointCount
);
828 vNorm
.reserve(nPointCount
);
830 // Calculate Normal vectors, and allocate curve data
832 for (i
= 0; i
< nPointCount
; i
++)
834 //Normal vector for a point will be calculated from its neighbour points
835 //except if it is in the start/end of the vector
836 sal_uInt16 nPointIdx1
= i
== 0 ? i
: i
- 1;
837 sal_uInt16 nPointIdx2
= i
== nPointCount
- 1 ? i
: i
+ 1;
839 Point aPoint
= rOutlinePoly
.GetPoint(nPointIdx2
)
840 - rOutlinePoly
.GetPoint(nPointIdx1
);
842 double fLen
= hypot(aPoint
.X(), aPoint
.Y());
846 //Rotate by 90 degree, and divide by length, to get normal vector
847 vNorm
.emplace_back(aPoint
.getY() * 1024 / fLen
,
848 -aPoint
.getX() * 1024 / fLen
);
852 vNorm
.emplace_back(0, 0);
854 vCurOutline
.emplace_back(Point());
855 vCurDistances
.push_back(0);
859 for( auto& rParagraph
: rTextArea
.vParagraphs
)
861 //calculate the actual outline length, and its align adjustments
865 // distance between the original and the current curve
866 double fCurvesDist
= rTextArea
.aBoundRect
.GetHeight() / 2.0
867 + rTextArea
.aBoundRect
.Top()
868 - rParagraph
.aBoundRect
.Center().Y();
869 // vertical alignment adjust
870 fCurvesDist
-= rTextArea
.nHAlignMove
;
872 for (i
= 0; i
< nPointCount
; i
++)
875 = rOutlinePoly
.GetPoint(i
) + vNorm
[i
] * fCurvesDist
/ 1024.0;
878 //calculate distances between points on the outer outline
879 const double fDx
= vCurOutline
[i
].X() - vCurOutline
[i
- 1].X();
880 const double fDy
= vCurOutline
[i
].Y() - vCurOutline
[i
- 1].Y();
881 vCurDistances
[i
] = hypot(fDx
, fDy
);
884 vCurDistances
[i
] = 0;
886 std::partial_sum(vCurDistances
.begin(), vCurDistances
.end(),
887 vCurDistances
.begin());
888 fCurWidth
= vCurDistances
[vCurDistances
.size() - 1];
891 for (auto& rDistance
: vCurDistances
)
892 rDistance
/= fCurWidth
;
895 // if the current outline is longer then the text to fit in,
896 // then we have to divide the bonus space between the
897 // before-/after- text area.
898 // fAdjust means how much space we put before the text.
899 if (fCurWidth
> rParagraph
.aBoundRect
.GetWidth())
902 = nAdjust
* (fCurWidth
- rParagraph
.aBoundRect
.GetWidth()) / 2;
905 fAdjust
= -1; // we need to shrink the text to fit the curve
907 for ( auto& rCharacter
: rParagraph
.vCharacters
)
909 for (tools::PolyPolygon
& rPolyPoly
: rCharacter
.vOutlines
)
911 tools::Rectangle
aBoundRect(rPolyPoly
.GetBoundRect());
912 double fx1
= aBoundRect
.Left() - nLeft
;
913 double fx2
= aBoundRect
.Right() - nLeft
;
915 double fParaRectWidth
= rParagraph
.aBoundRect
.GetWidth();
916 // Undo Horizontal alignment, hacked into poly coords,
917 // so we can calculate it the right way
918 double fHA
= (rFWData
.fHorizontalTextScaling
919 * rTextArea
.aBoundRect
.GetWidth()
920 - rParagraph
.aBoundRect
.GetWidth())
927 double fM1
= fx1
/ fParaRectWidth
;
928 double fM2
= fx2
/ fParaRectWidth
;
930 // if fAdjust<0, then it means, the text was longer, as
931 // the current outline, so we will skip the text scaling, and
932 // the text horizontal alignment adjustment
933 // so the text will be rendered just as long as the curve is.
936 fM1
= (fM1
* fParaRectWidth
+ fAdjust
) / fCurWidth
;
937 fM2
= (fM2
* fParaRectWidth
+ fAdjust
) / fCurWidth
;
939 // 0 <= fM1,fM2 <= 1 should be true, but rounding errors can
940 // make a small mistake.
941 // make sure they are >0 because GetPoint() need that
942 if (fM1
< 0) fM1
= 0;
943 if (fM2
< 0) fM2
= 0;
945 GetPoint(vCurOutline
, vCurDistances
, fM1
, fx1
, fy1
);
946 GetPoint(vCurOutline
, vCurDistances
, fM2
, fx2
, fy2
);
948 double fvx
= fy2
- fy1
;
949 double fvy
= - ( fx2
- fx1
);
950 fx1
= fx1
+ ( ( fx2
- fx1
) * 0.5 );
951 fy1
= fy1
+ ( ( fy2
- fy1
) * 0.5 );
953 double fAngle
= atan2( -fvx
, -fvy
);
954 double fL
= hypot( fvx
, fvy
);
957 SAL_WARN("svx", "FitTextOutlinesToShapeOutlines div-by-zero, abandon fit");
962 // Undo Vertical alignment hacked into poly coords
963 // We already calculated the right alignment into the curve
964 fL
= rTextArea
.nHAlignMove
;
967 rPolyPoly
.Rotate( Point( aBoundRect
.Center().X(), rParagraph
.aBoundRect
.Center().Y() ), sin( fAngle
), cos( fAngle
) );
968 rPolyPoly
.Move( static_cast<sal_Int32
>( ( fx1
+ fvx
)- aBoundRect
.Center().X() ), static_cast<sal_Int32
>( ( fy1
+ fvy
) - rParagraph
.aBoundRect
.Center().Y() ) );
975 // Fallback / old way to handle multiple lines:
976 // Every text lines use the same original outline (curve),
977 // it just scale character coordinates to fit to the right text line
978 // (curve), resulting wider/thinner space between characters
979 for (auto& rParagraph
: rTextArea
.vParagraphs
)
981 for (auto& rCharacter
: rParagraph
.vCharacters
)
983 for (tools::PolyPolygon
& rPolyPoly
: rCharacter
.vOutlines
)
985 tools::Rectangle
aBoundRect(rPolyPoly
.GetBoundRect());
986 double fx1
= aBoundRect
.Left() - nLeft
;
987 double fx2
= aBoundRect
.Right() - nLeft
;
989 double fM1
= fx1
/ static_cast<double>(nWidth
);
990 double fM2
= fx2
/ static_cast<double>(nWidth
);
992 GetPoint(rOutlinePoly
, vDistances
, fM1
, fx1
, fy1
);
993 GetPoint(rOutlinePoly
, vDistances
, fM2
, fx2
, fy2
);
995 double fvx
= fy2
- fy1
;
996 double fvy
= -(fx2
- fx1
);
997 fx1
= fx1
+ ((fx2
- fx1
) * 0.5);
998 fy1
= fy1
+ ((fy2
- fy1
) * 0.5);
1000 double fAngle
= atan2(-fvx
, -fvy
);
1001 double fL
= hypot(fvx
, fvy
);
1004 SAL_WARN("svx", "FitTextOutlinesToShapeOutlines div-by-zero, abandon fit");
1009 fL
= rTextArea
.aBoundRect
.GetHeight() / 2.0 + rTextArea
.aBoundRect
.Top() - rParagraph
.aBoundRect
.Center().Y();
1012 rPolyPoly
.Rotate( Point( aBoundRect
.Center().X(), rParagraph
.aBoundRect
.Center().Y() ), sin( fAngle
), cos( fAngle
) );
1013 rPolyPoly
.Move( static_cast<sal_Int32
>( ( fx1
+ fvx
)- aBoundRect
.Center().X() ), static_cast<sal_Int32
>( ( fy1
+ fvy
) - rParagraph
.aBoundRect
.Center().Y() ) );
1024 if ( ( nOutline2dIdx
+ 1 ) >= aOutlines2d
.Count() )
1026 const tools::Polygon
& rOutlinePoly( aOutlines2d
[ nOutline2dIdx
++ ] );
1027 const tools::Polygon
& rOutlinePoly2( aOutlines2d
[ nOutline2dIdx
++ ] );
1028 const sal_uInt16 nPointCount
= rOutlinePoly
.GetSize();
1029 const sal_uInt16 nPointCount2
= rOutlinePoly2
.GetSize();
1030 if ( ( nPointCount
> 1 ) && ( nPointCount2
> 1 ) )
1032 std::vector
< double > vDistances
;
1033 vDistances
.reserve( nPointCount
);
1034 std::vector
< double > vDistances2
;
1035 vDistances2
.reserve( nPointCount2
);
1036 CalcDistances( rOutlinePoly
, vDistances
);
1037 CalcDistances( rOutlinePoly2
, vDistances2
);
1038 for( auto& rParagraph
: rTextArea
.vParagraphs
)
1040 for ( auto& rCharacter
: rParagraph
.vCharacters
)
1042 for( tools::PolyPolygon
& rPolyPoly
: rCharacter
.vOutlines
)
1044 sal_uInt16 i
, nPolyCount
= rPolyPoly
.Count();
1045 for ( i
= 0; i
< nPolyCount
; i
++ )
1048 basegfx::B2DPolygon
aCandidate(rPolyPoly
[ i
].getB2DPolygon());
1050 if(aCandidate
.areControlPointsUsed())
1052 aCandidate
= basegfx::utils::adaptiveSubdivideByAngle(aCandidate
);
1055 // create local polygon copy to work on
1056 tools::Polygon
aLocalPoly(aCandidate
);
1058 InsertMissingOutlinePoints( vDistances
, rTextAreaBoundRect
, aLocalPoly
);
1059 InsertMissingOutlinePoints( vDistances2
, rTextAreaBoundRect
, aLocalPoly
);
1061 sal_uInt16 _nPointCount
= aLocalPoly
.GetSize();
1064 if (!nWidth
|| !nHeight
)
1065 throw o3tl::divide_by_zero();
1066 for (sal_uInt16 j
= 0; j
< _nPointCount
; ++j
)
1068 Point
& rPoint
= aLocalPoly
[ j
];
1069 rPoint
.AdjustX( -nLeft
);
1070 rPoint
.AdjustY( -nTop
);
1071 double fX
= static_cast<double>(rPoint
.X()) / static_cast<double>(nWidth
);
1072 double fY
= static_cast<double>(rPoint
.Y()) / static_cast<double>(nHeight
);
1074 double fx1
, fy1
, fx2
, fy2
;
1075 GetPoint( rOutlinePoly
, vDistances
, fX
, fx1
, fy1
);
1076 GetPoint( rOutlinePoly2
, vDistances2
, fX
, fx2
, fy2
);
1077 double fWidth
= fx2
- fx1
;
1078 double fHeight
= fy2
- fy1
;
1079 rPoint
.setX( static_cast<sal_Int32
>( fx1
+ fWidth
* fY
) );
1080 rPoint
.setY( static_cast<sal_Int32
>( fy1
+ fHeight
* fY
) );
1084 // write back polygon
1085 rPolyPoly
[i
] = std::move(aLocalPoly
);
1095 static rtl::Reference
<SdrObject
> CreateSdrObjectFromParagraphOutlines(
1096 const FWData
& rFWData
,
1097 const SdrObjCustomShape
& rSdrObjCustomShape
)
1099 rtl::Reference
<SdrObject
> pRet
;
1100 basegfx::B2DPolyPolygon aPolyPoly
;
1101 if ( !rFWData
.vTextAreas
.empty() )
1103 for ( const auto& rTextArea
: rFWData
.vTextAreas
)
1105 for ( const auto& rParagraph
: rTextArea
.vParagraphs
)
1107 for ( const auto& rCharacter
: rParagraph
.vCharacters
)
1109 for( const auto& rOutline
: rCharacter
.vOutlines
)
1111 aPolyPoly
.append( rOutline
.getB2DPolyPolygon() );
1117 pRet
= new SdrPathObj(
1118 rSdrObjCustomShape
.getSdrModelFromSdrObject(),
1119 SdrObjKind::Polygon
,
1120 std::move(aPolyPoly
));
1122 SfxItemSet
aSet(rSdrObjCustomShape
.GetMergedItemSet());
1123 aSet
.ClearItem( SDRATTR_TEXTDIRECTION
); //SJ: vertical writing is not required, by removing this item no outliner is created
1124 aSet
.Put(makeSdrShadowItem(false)); // #i37011# NO shadow for FontWork geometry
1125 pRet
->SetMergedItemSet( aSet
); // * otherwise we would crash, because the outliner tries to create a Paraobject, but there is no model
1131 Reference
< i18n::XBreakIterator
> EnhancedCustomShapeFontWork::mxBreakIterator
;
1133 Reference
< i18n::XBreakIterator
> const & EnhancedCustomShapeFontWork::GetBreakIterator()
1135 if ( !mxBreakIterator
.is() )
1137 const Reference
< uno::XComponentContext
>& xContext
= ::comphelper::getProcessComponentContext();
1138 mxBreakIterator
= i18n::BreakIterator::create(xContext
);
1140 return mxBreakIterator
;
1143 rtl::Reference
<SdrObject
> EnhancedCustomShapeFontWork::CreateFontWork(
1144 const SdrObject
* pShape2d
,
1145 const SdrObjCustomShape
& rSdrObjCustomShape
)
1147 rtl::Reference
<SdrObject
> pRet
;
1149 // calculating scaling factor is too slow
1150 if (comphelper::IsFuzzing())
1153 tools::PolyPolygon
aOutlines2d( GetOutlinesFromShape2d( pShape2d
) );
1154 sal_uInt16 nOutlinesCount2d
= aOutlines2d
.Count();
1155 if ( nOutlinesCount2d
)
1159 if(InitializeFontWorkData(rSdrObjCustomShape
, nOutlinesCount2d
, aFWData
))
1161 /* retrieves the horizontal scaling factor that has to be used
1162 to fit each paragraph text into its corresponding 2d outline */
1163 CalculateHorizontalScalingFactor(
1168 /* retrieving the Outlines for the each Paragraph. */
1169 if(!GetFontWorkOutline(
1171 rSdrObjCustomShape
))
1176 SdrTextHorzAdjust
eHorzAdjust(
1177 rSdrObjCustomShape
.GetMergedItem(SDRATTR_TEXT_HORZADJUST
).GetValue());
1178 bool bPPFontwork
= !rSdrObjCustomShape
.getSdrModelFromSdrObject().GetCompatibilityFlag(
1179 SdrCompatibilityFlag::LegacyFontwork
);
1180 FitTextOutlinesToShapeOutlines( aOutlines2d
, aFWData
, eHorzAdjust
, bPPFontwork
);
1182 pRet
= CreateSdrObjectFromParagraphOutlines(
1184 rSdrObjCustomShape
);
1190 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */