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
;
78 sal_Int32 nHAlignMove
= 0;
80 struct FWData
// representing the whole text
82 std::vector
< FWTextArea
> vTextAreas
;
83 double fHorizontalTextScaling
;
84 double fVerticalTextScaling
;
85 sal_uInt32 nMaxParagraphsPerTextArea
;
86 sal_Int32 nSingleLineHeight
;
93 static bool InitializeFontWorkData(
94 const SdrObjCustomShape
& rSdrObjCustomShape
,
95 const sal_uInt16 nOutlinesCount2d
,
99 bool bSingleLineMode
= false;
100 sal_uInt16 nTextAreaCount
= nOutlinesCount2d
;
101 if ( nOutlinesCount2d
& 1 )
102 bSingleLineMode
= true;
104 nTextAreaCount
>>= 1;
106 const SdrCustomShapeGeometryItem
& rGeometryItem( rSdrObjCustomShape
.GetMergedItem( SDRATTR_CUSTOMSHAPE_GEOMETRY
) );
107 const css::uno::Any
* pAny
= rGeometryItem
.GetPropertyValueByName( "TextPath", "ScaleX" );
109 *pAny
>>= rFWData
.bScaleX
;
111 rFWData
.bScaleX
= false;
113 if ( nTextAreaCount
)
115 rFWData
.bSingleLineMode
= bSingleLineMode
;
117 // setting the strings
118 OutlinerParaObject
* pParaObj(rSdrObjCustomShape
.GetOutlinerParaObject());
122 const EditTextObject
& rTextObj
= pParaObj
->GetTextObject();
123 sal_Int32 nParagraphsCount
= rTextObj
.GetParagraphCount();
125 // Collect all the lines from all paragraphs
126 std::vector
<int> aLineParaID
; // which para this line is in
127 std::vector
<int> aLineStart
; // where this line start in that para
128 std::vector
<int> aLineLength
;
129 std::vector
<OUString
> aParaText
;
130 for (sal_Int32 nPara
= 0; nPara
< nParagraphsCount
; ++nPara
)
132 aParaText
.push_back(rTextObj
.GetText(nPara
));
134 sal_Int32 nPrevPos
= 0;
137 // search line break.
138 if (!rSdrObjCustomShape
.getSdrModelFromSdrObject().GetCompatibilityFlag(
139 SdrCompatibilityFlag::LegacyFontwork
))
140 nPos
= aParaText
[nPara
].indexOf(sal_Unicode(u
'\1'), nPrevPos
);
142 nPos
= -1; // tdf#148000: ignore line breaks in legacy fontworks
144 aLineParaID
.push_back(nPara
);
145 aLineStart
.push_back(nPrevPos
);
146 aLineLength
.push_back((nPos
>= 0 ? nPos
: aParaText
[nPara
].getLength())
152 sal_Int32 nLinesLeft
= aLineParaID
.size();
154 rFWData
.nMaxParagraphsPerTextArea
= ((nLinesLeft
- 1) / nTextAreaCount
) + 1;
156 while (nLinesLeft
&& nTextAreaCount
)
158 FWTextArea aTextArea
;
159 sal_Int32 nLinesInPara
= ((nLinesLeft
- 1) / nTextAreaCount
) + 1;
160 for (sal_Int32 i
= 0; i
< nLinesInPara
; ++i
, ++nLine
)
162 FWParagraphData aParagraphData
;
163 aParagraphData
.aString
= aParaText
[aLineParaID
[nLine
]].subView(
164 aLineStart
[nLine
], aLineLength
[nLine
]);
166 // retrieving some paragraph attributes
167 const SfxItemSet
& rParaSet
= rTextObj
.GetParaAttribs(aLineParaID
[nLine
]);
168 aParagraphData
.nFrameDirection
= rParaSet
.Get(EE_PARA_WRITINGDIR
).GetValue();
169 aTextArea
.vParagraphs
.push_back(aParagraphData
);
171 rFWData
.vTextAreas
.push_back(aTextArea
);
172 nLinesLeft
-= nLinesInPara
;
182 static double GetLength( const tools::Polygon
& rPolygon
)
185 if ( rPolygon
.GetSize() > 1 )
187 sal_uInt16 nCount
= rPolygon
.GetSize();
189 fLength
+= rPolygon
.CalcDistance( nCount
, nCount
- 1 );
195 /* CalculateHorizontalScalingFactor returns the horizontal scaling factor for
196 the whole text object, so that each text will match its corresponding 2d Outline */
197 static void CalculateHorizontalScalingFactor(
198 const SdrObjCustomShape
& rSdrObjCustomShape
,
200 const tools::PolyPolygon
& rOutline2d
)
202 double fScalingFactor
= 1.0;
203 rFWData
.fVerticalTextScaling
= 1.0;
206 bool bSingleLineMode
= false;
207 sal_uInt16 nOutlinesCount2d
= rOutline2d
.Count();
210 const SvxFontItem
& rFontItem( rSdrObjCustomShape
.GetMergedItem( EE_CHAR_FONTINFO
) );
211 const SvxFontHeightItem
& rFontHeight( rSdrObjCustomShape
.GetMergedItem( EE_CHAR_FONTHEIGHT
) );
212 sal_Int32 nFontSize
= rFontHeight
.GetHeight();
215 aFont
.SetFontHeight( nFontSize
);
217 aFont
.SetFontHeight( rSdrObjCustomShape
.GetLogicRect().GetHeight() / rFWData
.nMaxParagraphsPerTextArea
);
219 aFont
.SetAlignment( ALIGN_TOP
);
220 aFont
.SetFamilyName( rFontItem
.GetFamilyName() );
221 aFont
.SetFamily( rFontItem
.GetFamily() );
222 aFont
.SetStyleName( rFontItem
.GetStyleName() );
223 const SvxPostureItem
& rPostureItem
= rSdrObjCustomShape
.GetMergedItem( EE_CHAR_ITALIC
);
224 aFont
.SetItalic( rPostureItem
.GetPosture() );
226 const SvxWeightItem
& rWeightItem
= rSdrObjCustomShape
.GetMergedItem( EE_CHAR_WEIGHT
);
227 aFont
.SetWeight( rWeightItem
.GetWeight() );
228 aFont
.SetOrientation( 0_deg10
);
229 // initializing virtual device
231 ScopedVclPtrInstance
< VirtualDevice
> pVirDev(DeviceFormat::WITHOUT_ALPHA
);
232 pVirDev
->SetMapMode(MapMode(MapUnit::Map100thMM
));
233 pVirDev
->SetFont( aFont
);
234 pVirDev
->SetAntialiasing( AntialiasingFlags::DisableText
);
236 if ( nOutlinesCount2d
& 1 )
237 bSingleLineMode
= true;
239 // In case of rFWData.bScaleX == true it loops with reduced font size until the current run
240 // results in a fScalingFactor >=1.0. The fact, that case rFWData.bScaleX == true keeps font
241 // size if possible, is not done here with scaling factor 1 but is done in method
242 // FitTextOutlinesToShapeOutlines()
246 bool bScalingFactorDefined
= false; // New calculation for each font size
247 for( const auto& rTextArea
: rFWData
.vTextAreas
)
249 // calculating the width of the corresponding 2d text area
250 double fWidth
= GetLength( rOutline2d
.GetObject( i
++ ) );
251 if ( !bSingleLineMode
)
253 fWidth
+= GetLength( rOutline2d
.GetObject( i
++ ) );
257 for( const auto& rParagraph
: rTextArea
.vParagraphs
)
259 double fTextWidth
= pVirDev
->GetTextWidth( rParagraph
.aString
);
260 if ( fTextWidth
> 0.0 )
262 double fScale
= fWidth
/ fTextWidth
;
263 if ( !bScalingFactorDefined
)
265 fScalingFactor
= fScale
;
266 bScalingFactorDefined
= true;
268 else if (fScale
< fScalingFactor
)
270 fScalingFactor
= fScale
;
276 if (fScalingFactor
< 1.0)
279 aFont
.SetFontHeight( nFontSize
);
280 pVirDev
->SetFont( aFont
);
283 while (rFWData
.bScaleX
&& fScalingFactor
< 1.0 && nFontSize
> 1 );
286 rFWData
.fVerticalTextScaling
= static_cast<double>(nFontSize
) / rFontHeight
.GetHeight();
288 rFWData
.fHorizontalTextScaling
= fScalingFactor
;
291 static void GetTextAreaOutline(
292 const FWData
& rFWData
,
293 const SdrObjCustomShape
& rSdrObjCustomShape
,
294 FWTextArea
& rTextArea
,
295 bool bSameLetterHeights
)
297 bool bIsVertical(rSdrObjCustomShape
.IsVerticalWriting());
298 sal_Int32 nVerticalOffset
= rFWData
.nMaxParagraphsPerTextArea
> rTextArea
.vParagraphs
.size()
299 ? rFWData
.nSingleLineHeight
/ 2 : 0;
301 for( auto& rParagraph
: rTextArea
.vParagraphs
)
303 const OUString
& rText
= rParagraph
.aString
;
304 if ( !rText
.isEmpty() )
306 // generating vcl/font
307 sal_uInt16 nScriptType
= i18n::ScriptType::LATIN
;
308 Reference
< i18n::XBreakIterator
> xBI( EnhancedCustomShapeFontWork::GetBreakIterator() );
311 nScriptType
= xBI
->getScriptType( rText
, 0 );
312 if( i18n::ScriptType::WEAK
== nScriptType
)
314 sal_Int32 nChg
= xBI
->endOfScript( rText
, 0, nScriptType
);
315 if (nChg
< rText
.getLength() && nChg
>= 0)
316 nScriptType
= xBI
->getScriptType( rText
, nChg
);
318 nScriptType
= i18n::ScriptType::LATIN
;
321 sal_uInt16 nFntItm
= EE_CHAR_FONTINFO
;
322 if ( nScriptType
== i18n::ScriptType::COMPLEX
)
323 nFntItm
= EE_CHAR_FONTINFO_CTL
;
324 else if ( nScriptType
== i18n::ScriptType::ASIAN
)
325 nFntItm
= EE_CHAR_FONTINFO_CJK
;
326 const SvxFontItem
& rFontItem
= static_cast<const SvxFontItem
&>(rSdrObjCustomShape
.GetMergedItem( nFntItm
));
329 aFont
.SetFontHeight( rFWData
.nSingleLineHeight
);
331 aFont
.SetAlignment( ALIGN_TOP
);
333 aFont
.SetFamilyName( rFontItem
.GetFamilyName() );
334 aFont
.SetFamily( rFontItem
.GetFamily() );
335 aFont
.SetStyleName( rFontItem
.GetStyleName() );
336 aFont
.SetOrientation( 0_deg10
);
338 const SvxPostureItem
& rPostureItem
= rSdrObjCustomShape
.GetMergedItem( EE_CHAR_ITALIC
);
339 aFont
.SetItalic( rPostureItem
.GetPosture() );
341 const SvxWeightItem
& rWeightItem
= rSdrObjCustomShape
.GetMergedItem( EE_CHAR_WEIGHT
);
342 aFont
.SetWeight( rWeightItem
.GetWeight() );
344 // initializing virtual device
345 ScopedVclPtrInstance
< VirtualDevice
> pVirDev(DeviceFormat::WITHOUT_ALPHA
);
346 pVirDev
->SetMapMode(MapMode(MapUnit::Map100thMM
));
347 pVirDev
->SetFont( aFont
);
348 pVirDev
->SetAntialiasing( AntialiasingFlags::DisableText
);
350 pVirDev
->EnableRTL();
351 if ( rParagraph
.nFrameDirection
== SvxFrameDirection::Horizontal_RL_TB
)
352 pVirDev
->SetLayoutMode( vcl::text::ComplexTextLayoutFlags::BiDiRtl
);
354 const SvxCharScaleWidthItem
& rCharScaleWidthItem
= rSdrObjCustomShape
.GetMergedItem( EE_CHAR_FONTWIDTH
);
355 sal_uInt16 nCharScaleWidth
= rCharScaleWidthItem
.GetValue();
356 sal_Int32 nWidth
= 0;
361 // vertical _> each single character needs to be rotated by 90
363 sal_Int32 nHeight
= 0;
364 tools::Rectangle aSingleCharacterUnion
;
365 for ( i
= 0; i
< rText
.getLength(); i
++ )
367 FWCharacterData aCharacterData
;
368 OUString
aCharText( rText
[ i
] );
369 if ( pVirDev
->GetTextOutlines( aCharacterData
.vOutlines
, aCharText
, 0, 0, -1, nWidth
, {} ) )
371 sal_Int32 nTextWidth
= pVirDev
->GetTextWidth( aCharText
);
372 if ( aCharacterData
.vOutlines
.empty() )
374 nHeight
+= rFWData
.nSingleLineHeight
;
378 for ( auto& rOutline
: aCharacterData
.vOutlines
)
381 rOutline
.Rotate( Point( nTextWidth
/ 2, rFWData
.nSingleLineHeight
/ 2 ), 900_deg10
);
382 aCharacterData
.aBoundRect
.Union( rOutline
.GetBoundRect() );
384 for ( auto& rOutline
: aCharacterData
.vOutlines
)
386 sal_Int32 nM
= - aCharacterData
.aBoundRect
.Left() + nHeight
;
387 rOutline
.Move( nM
, 0 );
388 aCharacterData
.aBoundRect
.Move( nM
, 0 );
390 nHeight
+= aCharacterData
.aBoundRect
.GetWidth() + ( rFWData
.nSingleLineHeight
/ 5 );
391 aSingleCharacterUnion
.Union( aCharacterData
.aBoundRect
);
394 rParagraph
.vCharacters
.push_back( aCharacterData
);
396 for ( auto& rCharacter
: rParagraph
.vCharacters
)
398 for ( auto& rOutline
: rCharacter
.vOutlines
)
400 rOutline
.Move( ( aSingleCharacterUnion
.GetWidth() - rCharacter
.aBoundRect
.GetWidth() ) / 2, 0 );
407 if ( ( nCharScaleWidth
!= 100 ) && nCharScaleWidth
)
408 { // applying character spacing
409 pVirDev
->GetTextArray( rText
, &aDXArry
);
410 FontMetric
aFontMetric( pVirDev
->GetFontMetric() );
411 aFont
.SetAverageFontWidth( static_cast<sal_Int32
>( static_cast<double>(aFontMetric
.GetAverageFontWidth()) * ( double(100) / static_cast<double>(nCharScaleWidth
) ) ) );
412 pVirDev
->SetFont( aFont
);
414 FWCharacterData aCharacterData
;
415 if ( pVirDev
->GetTextOutlines( aCharacterData
.vOutlines
, rText
, 0, 0, -1, nWidth
, aDXArry
) )
417 rParagraph
.vCharacters
.push_back( aCharacterData
);
421 // GetTextOutlines failed what usually means that it is
422 // not implemented. To make FontWork not fail (it is
423 // dependent of graphic content to get a Range) create
424 // a rectangle substitution for now
425 pVirDev
->GetTextArray( rText
, &aDXArry
);
426 aCharacterData
.vOutlines
.clear();
430 for(size_t a(0); a
< aDXArry
.size(); a
++)
432 const basegfx::B2DPolygon
aPolygon(
433 basegfx::utils::createPolygonFromRect(
435 0 == a
? 0 : aDXArry
[a
- 1],
438 aFont
.GetFontHeight()
440 aCharacterData
.vOutlines
.push_back(tools::PolyPolygon(tools::Polygon(aPolygon
)));
445 const basegfx::B2DPolygon
aPolygon(
446 basegfx::utils::createPolygonFromRect(
450 aDXArry
.empty() ? 10 : aDXArry
.back(),
451 aFont
.GetFontHeight()
453 aCharacterData
.vOutlines
.push_back(tools::PolyPolygon(tools::Polygon(aPolygon
)));
457 rParagraph
.vCharacters
.push_back( aCharacterData
);
461 // vertical alignment
462 for ( auto& rCharacter
: rParagraph
.vCharacters
)
464 for( tools::PolyPolygon
& rPolyPoly
: rCharacter
.vOutlines
)
466 if ( nVerticalOffset
)
467 rPolyPoly
.Move( 0, nVerticalOffset
);
469 // retrieving the boundrect for the paragraph
470 tools::Rectangle
aBoundRect( rPolyPoly
.GetBoundRect() );
471 rParagraph
.aBoundRect
.Union( aBoundRect
);
475 // updating the boundrect for the text area by merging the current paragraph boundrect
476 if ( rParagraph
.aBoundRect
.IsEmpty() )
478 if ( rTextArea
.aBoundRect
.IsEmpty() )
479 rTextArea
.aBoundRect
= tools::Rectangle( Point( 0, 0 ), Size( 1, rFWData
.nSingleLineHeight
) );
481 rTextArea
.aBoundRect
.AdjustBottom(rFWData
.nSingleLineHeight
);
485 tools::Rectangle
& rParagraphBoundRect
= rParagraph
.aBoundRect
;
486 rTextArea
.aBoundRect
.Union( rParagraphBoundRect
);
488 if ( bSameLetterHeights
)
490 for ( auto& rCharacter
: rParagraph
.vCharacters
)
492 for( auto& rOutline
: rCharacter
.vOutlines
)
494 tools::Rectangle
aPolyPolyBoundRect( rOutline
.GetBoundRect() );
495 if (aPolyPolyBoundRect
.GetHeight() != rParagraphBoundRect
.GetHeight() && aPolyPolyBoundRect
.GetHeight())
496 rOutline
.Scale( 1.0, static_cast<double>(rParagraphBoundRect
.GetHeight()) / aPolyPolyBoundRect
.GetHeight() );
497 aPolyPolyBoundRect
= rOutline
.GetBoundRect();
498 sal_Int32 nMove
= aPolyPolyBoundRect
.Top() - rParagraphBoundRect
.Top();
500 rOutline
.Move( 0, -nMove
);
506 nVerticalOffset
-= rFWData
.nSingleLineHeight
;
508 nVerticalOffset
+= rFWData
.nSingleLineHeight
;
512 static bool GetFontWorkOutline(
514 const SdrObjCustomShape
& rSdrObjCustomShape
)
516 SdrTextHorzAdjust
eHorzAdjust(rSdrObjCustomShape
.GetMergedItem( SDRATTR_TEXT_HORZADJUST
).GetValue());
517 drawing::TextFitToSizeType
const eFTS(rSdrObjCustomShape
.GetMergedItem( SDRATTR_TEXT_FITTOSIZE
).GetValue());
519 bool bSameLetterHeights
= false;
520 const SdrCustomShapeGeometryItem
& rGeometryItem(rSdrObjCustomShape
.GetMergedItem( SDRATTR_CUSTOMSHAPE_GEOMETRY
));
521 const css::uno::Any
* pAny
= rGeometryItem
.GetPropertyValueByName( "TextPath", "SameLetterHeights" );
523 *pAny
>>= bSameLetterHeights
;
525 const SvxFontHeightItem
& rFontHeight( rSdrObjCustomShape
.GetMergedItem( EE_CHAR_FONTHEIGHT
) );
527 rFWData
.nSingleLineHeight
= rFWData
.fVerticalTextScaling
* rFontHeight
.GetHeight();
529 rFWData
.nSingleLineHeight
= static_cast<sal_Int32
>( ( static_cast<double>( rSdrObjCustomShape
.GetLogicRect().GetHeight() )
530 / rFWData
.nMaxParagraphsPerTextArea
) * rFWData
.fHorizontalTextScaling
);
532 if (rFWData
.nSingleLineHeight
== SAL_MIN_INT32
)
535 for ( auto& rTextArea
: rFWData
.vTextAreas
)
543 if (eFTS
== drawing::TextFitToSizeType_ALLLINES
||
544 // tdf#97630 interpret PROPORTIONAL same as ALLLINES so we don't
545 // need another ODF attribute!
546 eFTS
== drawing::TextFitToSizeType_PROPORTIONAL
)
548 for ( auto& rParagraph
: rTextArea
.vParagraphs
)
550 sal_Int32 nParaWidth
= rParagraph
.aBoundRect
.GetWidth();
553 double fScale
= static_cast<double>(rTextArea
.aBoundRect
.GetWidth()) / nParaWidth
;
555 for ( auto& rCharacter
: rParagraph
.vCharacters
)
557 for( auto& rOutline
: rCharacter
.vOutlines
)
559 rOutline
.Scale( fScale
, 1.0 );
565 else if (rFWData
.bScaleX
)
567 const SdrTextVertAdjust nVertJustify
= rSdrObjCustomShape
.GetMergedItem( SDRATTR_TEXT_VERTADJUST
).GetValue();
568 double fFactor
= nVertJustify
== SdrTextVertAdjust::SDRTEXTVERTADJUST_BOTTOM
? -0.5 : ( nVertJustify
== SdrTextVertAdjust::SDRTEXTVERTADJUST_TOP
? 0.5 : 0 );
570 for ( auto& rParagraph
: rTextArea
.vParagraphs
)
572 sal_Int32 nHorzDiff
= 0;
573 sal_Int32 nVertDiff
= static_cast<double>( rFWData
.nSingleLineHeight
) * fFactor
* ( rTextArea
.vParagraphs
.size() - 1 );
574 rTextArea
.nHAlignMove
= nVertDiff
;
576 if ( eHorzAdjust
== SDRTEXTHORZADJUST_CENTER
)
577 nHorzDiff
= ( rFWData
.fHorizontalTextScaling
* rTextArea
.aBoundRect
.GetWidth() - rParagraph
.aBoundRect
.GetWidth() ) / 2;
578 else if ( eHorzAdjust
== SDRTEXTHORZADJUST_RIGHT
)
579 nHorzDiff
= ( rFWData
.fHorizontalTextScaling
* rTextArea
.aBoundRect
.GetWidth() - rParagraph
.aBoundRect
.GetWidth() );
581 if (nHorzDiff
|| nVertDiff
)
583 for ( auto& rCharacter
: rParagraph
.vCharacters
)
585 for( auto& rOutline
: rCharacter
.vOutlines
)
587 rOutline
.Move( nHorzDiff
, nVertDiff
);
595 switch( eHorzAdjust
)
597 case SDRTEXTHORZADJUST_RIGHT
:
598 case SDRTEXTHORZADJUST_CENTER
:
600 for ( auto& rParagraph
: rTextArea
.vParagraphs
)
602 sal_Int32 nHorzDiff
= 0;
603 if ( eHorzAdjust
== SDRTEXTHORZADJUST_CENTER
)
604 nHorzDiff
= ( rTextArea
.aBoundRect
.GetWidth() - rParagraph
.aBoundRect
.GetWidth() ) / 2;
605 else if ( eHorzAdjust
== SDRTEXTHORZADJUST_RIGHT
)
606 nHorzDiff
= ( rTextArea
.aBoundRect
.GetWidth() - rParagraph
.aBoundRect
.GetWidth() );
609 for ( auto& rCharacter
: rParagraph
.vCharacters
)
611 for( auto& rOutline
: rCharacter
.vOutlines
)
613 rOutline
.Move( nHorzDiff
, 0 );
621 case SDRTEXTHORZADJUST_BLOCK
: break; // don't know
622 case SDRTEXTHORZADJUST_LEFT
: break; // already left aligned -> nothing to do
630 static basegfx::B2DPolyPolygon
GetOutlinesFromShape2d( const SdrObject
* pShape2d
)
632 basegfx::B2DPolyPolygon aOutlines2d
;
634 SdrObjListIter
aObjListIter( *pShape2d
, SdrIterMode::DeepWithGroups
);
635 while( aObjListIter
.IsMore() )
637 SdrObject
* pPartObj
= aObjListIter
.Next();
638 if ( auto pPathObj
= dynamic_cast<const SdrPathObj
*>( pPartObj
))
640 basegfx::B2DPolyPolygon
aCandidate(pPathObj
->GetPathPoly());
641 if(aCandidate
.areControlPointsUsed())
643 aCandidate
= basegfx::utils::adaptiveSubdivideByAngle(aCandidate
);
645 aOutlines2d
.append(aCandidate
);
652 static void CalcDistances( const tools::Polygon
& rPoly
, std::vector
< double >& rDistances
)
654 sal_uInt16 i
, nCount
= rPoly
.GetSize();
658 for ( i
= 0; i
< nCount
; i
++ )
660 double fDistance
= i
? rPoly
.CalcDistance( i
, i
- 1 ) : 0.0;
661 rDistances
.push_back( fDistance
);
663 std::partial_sum( rDistances
.begin(), rDistances
.end(), rDistances
.begin() );
664 double fLength
= rDistances
[ rDistances
.size() - 1 ];
667 for ( auto& rDistance
: rDistances
)
668 rDistance
/= fLength
;
672 static void InsertMissingOutlinePoints( const std::vector
< double >& rDistances
,
673 const tools::Rectangle
& rTextAreaBoundRect
, tools::Polygon
& rPoly
)
675 sal_uInt16 nSize
= rPoly
.GetSize();
679 tools::Long nTextWidth
= rTextAreaBoundRect
.GetWidth();
682 throw o3tl::divide_by_zero();
684 double fLastDistance
= 0.0;
685 for (sal_uInt16 i
= 0; i
< nSize
; ++i
)
687 Point
& rPoint
= rPoly
[ i
];
688 double fDistance
= static_cast<double>( rPoint
.X() - rTextAreaBoundRect
.Left() ) / static_cast<double>(nTextWidth
);
691 if ( fDistance
> fLastDistance
)
693 std::vector
< double >::const_iterator aIter
= std::upper_bound( rDistances
.begin(), rDistances
.end(), fLastDistance
);
694 if ( aIter
!= rDistances
.end() && ( *aIter
> fLastDistance
) && ( *aIter
< fDistance
) )
696 Point
& rPt0
= rPoly
[ i
- 1 ];
697 sal_Int32 fX
= rPoint
.X() - rPt0
.X();
698 sal_Int32 fY
= rPoint
.Y() - rPt0
.Y();
699 double fd
= ( 1.0 / ( fDistance
- fLastDistance
) ) * ( *aIter
- fLastDistance
);
700 rPoly
.Insert( i
, Point( static_cast<sal_Int32
>( rPt0
.X() + fX
* fd
), static_cast<sal_Int32
>( rPt0
.Y() + fY
* fd
) ) );
704 else if ( fDistance
< fLastDistance
)
706 std::vector
< double >::const_iterator aIter
= std::lower_bound( rDistances
.begin(), rDistances
.end(), fLastDistance
);
707 if ( aIter
!= rDistances
.begin() )
710 if ( ( *aIter
> fDistance
) && ( *aIter
< fLastDistance
) )
712 Point
& rPt0
= rPoly
[ i
- 1 ];
713 sal_Int32 fX
= rPoint
.X() - rPt0
.X();
714 sal_Int32 fY
= rPoint
.Y() - rPt0
.Y();
715 double fd
= ( 1.0 / ( fDistance
- fLastDistance
) ) * ( *aIter
- fLastDistance
);
716 rPoly
.Insert( i
, Point( static_cast<sal_Int32
>( rPt0
.X() + fX
* fd
), static_cast<sal_Int32
>( rPt0
.Y() + fY
* fd
) ) );
722 fLastDistance
= fDistance
;
726 //only 2 types used: 'const tools::Polygon&' and 'const std::vector<Point>&'
728 static void GetPoint( T rPoly
, const std::vector
< double >& rDistances
, const double& fX
, double& fx1
, double& fy1
)
731 if (rPoly
.size() <= 1)
734 std::vector
< double >::const_iterator aIter
= std::lower_bound( rDistances
.begin(), rDistances
.end(), fX
);
735 sal_uInt16 nIdx
= sal::static_int_cast
<sal_uInt16
>( std::distance( rDistances
.begin(), aIter
) );
736 if ( aIter
== rDistances
.end() )
738 const Point
& rPt
= rPoly
[ nIdx
];
741 if ( !nIdx
|| ( aIter
== rDistances
.end() ) || rtl::math::approxEqual( *aIter
, fX
) )
744 nIdx
= sal::static_int_cast
<sal_uInt16
>( std::distance( rDistances
.begin(), aIter
) );
745 double fDist0
= *( aIter
- 1 );
746 double fd
= ( 1.0 / ( *aIter
- fDist0
) ) * ( fX
- fDist0
);
747 const Point
& rPt2
= rPoly
[ nIdx
- 1 ];
748 double fWidth
= rPt
.X() - rPt2
.X();
749 double fHeight
= rPt
.Y() - rPt2
.Y();
752 fx1
= rPt2
.X() + fWidth
;
753 fy1
= rPt2
.Y() + fHeight
;
756 static void FitTextOutlinesToShapeOutlines(const tools::PolyPolygon
& aOutlines2d
, FWData
& rFWData
,
757 SdrTextHorzAdjust eHorzAdjust
, bool bPPFontwork
)
759 sal_uInt16 nOutline2dIdx
= 0;
760 for( auto& rTextArea
: rFWData
.vTextAreas
)
762 tools::Rectangle rTextAreaBoundRect
= rTextArea
.aBoundRect
;
763 sal_Int32 nLeft
= rTextAreaBoundRect
.Left();
764 sal_Int32 nTop
= rTextAreaBoundRect
.Top();
765 sal_Int32 nWidth
= rTextAreaBoundRect
.GetWidth();
766 sal_Int32 nHeight
= rTextAreaBoundRect
.GetHeight();
770 nWidth
*= rFWData
.fHorizontalTextScaling
;
773 if ( rFWData
.bSingleLineMode
&& nHeight
&& nWidth
)
775 if ( nOutline2dIdx
>= aOutlines2d
.Count() )
777 const tools::Polygon
& rOutlinePoly( aOutlines2d
[ nOutline2dIdx
++ ] );
778 const sal_uInt16 nPointCount
= rOutlinePoly
.GetSize();
779 if ( nPointCount
> 1 )
781 std::vector
< double > vDistances
;
782 vDistances
.reserve( nPointCount
);
783 CalcDistances( rOutlinePoly
, vDistances
);
785 if ( !vDistances
.empty() )
787 // horizontal alignment: how much we have to move text to the right.
791 case SDRTEXTHORZADJUST_RIGHT
:
792 nAdjust
= 2; // 2 half of the possible
794 case SDRTEXTHORZADJUST_CENTER
:
795 nAdjust
= 1; // 1 half of the possible
797 case SDRTEXTHORZADJUST_BLOCK
:
798 nAdjust
= -1; // don't know what it is, so don't even align
800 case SDRTEXTHORZADJUST_LEFT
:
801 nAdjust
= 0; // no need to move
805 if (bPPFontwork
&& rTextArea
.vParagraphs
.size() > 1 && nAdjust
>= 0)
807 // If we have multiple lines of text to fit to the outline (curve)
808 // then we have to be able to calculate outer versions of the outline
809 // where we can fit the next lines of texts
810 // those outer lines will be wider (or shorter) as the original outline
811 // and probably will looks different as the original outline.
813 // for example if we have an outline like this:
815 // then the middle part will have the same normals, so distances there,
816 // will not change for an outer outline
817 // while the points near the edge will have different normals,
818 // distances around there will increase for an outer (wider) outline
820 //Normal vectors for every rOutlinePoly point. 1024 long
821 std::vector
<Point
> vNorm
;
822 //wider curve path points, for current paragraph (rOutlinePoly + vNorm*line)
823 std::vector
<Point
> vCurOutline
;
824 //distances between points of this wider curve
825 std::vector
<double> vCurDistances
;
827 vCurDistances
.reserve(nPointCount
);
828 vCurOutline
.reserve(nPointCount
);
829 vNorm
.reserve(nPointCount
);
831 // Calculate Normal vectors, and allocate curve data
833 for (i
= 0; i
< nPointCount
; i
++)
835 //Normal vector for a point will be calculated from its neighbour points
836 //except if it is in the start/end of the vector
837 sal_uInt16 nPointIdx1
= i
== 0 ? i
: i
- 1;
838 sal_uInt16 nPointIdx2
= i
== nPointCount
- 1 ? i
: i
+ 1;
840 Point aPoint
= rOutlinePoly
.GetPoint(nPointIdx2
)
841 - rOutlinePoly
.GetPoint(nPointIdx1
);
843 double fLen
= sqrt(aPoint
.X() * aPoint
.X() + aPoint
.Y() * aPoint
.Y());
847 //Rotate by 90 degree, and divide by length, to get normal vector
848 vNorm
.emplace_back(aPoint
.getY() * 1024 / fLen
,
849 -aPoint
.getX() * 1024 / fLen
);
853 vNorm
.emplace_back(0, 0);
855 vCurOutline
.emplace_back(Point());
856 vCurDistances
.push_back(0);
860 for( auto& rParagraph
: rTextArea
.vParagraphs
)
862 //calculate the actual outline length, and its align adjustments
866 // distance between the original and the current curve
867 double fCurvesDist
= rTextArea
.aBoundRect
.GetHeight() / 2.0
868 + rTextArea
.aBoundRect
.Top()
869 - rParagraph
.aBoundRect
.Center().Y();
870 // vertical alignment adjust
871 fCurvesDist
-= rTextArea
.nHAlignMove
;
873 for (i
= 0; i
< nPointCount
; i
++)
876 = rOutlinePoly
.GetPoint(i
) + vNorm
[i
] * fCurvesDist
/ 1024.0;
879 //calculate distances between points on the outer outline
880 const double fDx
= vCurOutline
[i
].X() - vCurOutline
[i
- 1].X();
881 const double fDy
= vCurOutline
[i
].Y() - vCurOutline
[i
- 1].Y();
882 vCurDistances
[i
] = sqrt(fDx
* fDx
+ fDy
* fDy
);
885 vCurDistances
[i
] = 0;
887 std::partial_sum(vCurDistances
.begin(), vCurDistances
.end(),
888 vCurDistances
.begin());
889 fCurWidth
= vCurDistances
[vCurDistances
.size() - 1];
892 for (auto& rDistance
: vCurDistances
)
893 rDistance
/= fCurWidth
;
896 // if the current outline is longer then the text to fit in,
897 // then we have to divide the bonus space between the
898 // before-/after- text area.
899 // fAdjust means how much space we put before the text.
900 if (fCurWidth
> rParagraph
.aBoundRect
.GetWidth())
903 = nAdjust
* (fCurWidth
- rParagraph
.aBoundRect
.GetWidth()) / 2;
906 fAdjust
= -1; // we need to shrink the text to fit the curve
908 for ( auto& rCharacter
: rParagraph
.vCharacters
)
910 for (tools::PolyPolygon
& rPolyPoly
: rCharacter
.vOutlines
)
912 tools::Rectangle
aBoundRect(rPolyPoly
.GetBoundRect());
913 double fx1
= aBoundRect
.Left() - nLeft
;
914 double fx2
= aBoundRect
.Right() - nLeft
;
916 double fParaRectWidth
= rParagraph
.aBoundRect
.GetWidth();
917 // Undo Horizontal alignment, hacked into poly coords,
918 // so we can calculate it the right way
919 double fHA
= (rFWData
.fHorizontalTextScaling
920 * rTextArea
.aBoundRect
.GetWidth()
921 - rParagraph
.aBoundRect
.GetWidth())
928 double fM1
= fx1
/ fParaRectWidth
;
929 double fM2
= fx2
/ fParaRectWidth
;
931 // if fAdjust<0, then it means, the text was longer, as
932 // the current outline, so we will skip the text scaling, and
933 // the text horizontal alignment adjustment
934 // so the text will be rendered just as long as the curve is.
937 fM1
= (fM1
* fParaRectWidth
+ fAdjust
) / fCurWidth
;
938 fM2
= (fM2
* fParaRectWidth
+ fAdjust
) / fCurWidth
;
940 // 0 <= fM1,fM2 <= 1 should be true, but rounding errors can
941 // make a small mistake.
942 // make sure they are >0 because GetPoint() need that
943 if (fM1
< 0) fM1
= 0;
944 if (fM2
< 0) fM2
= 0;
946 GetPoint(vCurOutline
, vCurDistances
, fM1
, fx1
, fy1
);
947 GetPoint(vCurOutline
, vCurDistances
, fM2
, fx2
, fy2
);
949 double fvx
= fy2
- fy1
;
950 double fvy
= - ( fx2
- fx1
);
951 fx1
= fx1
+ ( ( fx2
- fx1
) * 0.5 );
952 fy1
= fy1
+ ( ( fy2
- fy1
) * 0.5 );
954 double fAngle
= atan2( -fvx
, -fvy
);
955 double fL
= hypot( fvx
, fvy
);
958 SAL_WARN("svx", "FitTextOutlinesToShapeOutlines div-by-zero, abandon fit");
963 // Undo Vertical alignment hacked into poly coords
964 // We already calculated the right alignment into the curve
965 fL
= rTextArea
.nHAlignMove
;
968 rPolyPoly
.Rotate( Point( aBoundRect
.Center().X(), rParagraph
.aBoundRect
.Center().Y() ), sin( fAngle
), cos( fAngle
) );
969 rPolyPoly
.Move( static_cast<sal_Int32
>( ( fx1
+ fvx
)- aBoundRect
.Center().X() ), static_cast<sal_Int32
>( ( fy1
+ fvy
) - rParagraph
.aBoundRect
.Center().Y() ) );
976 // Fallback / old way to handle multiple lines:
977 // Every text lines use the same original outline (curve),
978 // it just scale character coordinates to fit to the right text line
979 // (curve), resulting wider/thinner space between characters
980 for (auto& rParagraph
: rTextArea
.vParagraphs
)
982 for (auto& rCharacter
: rParagraph
.vCharacters
)
984 for (tools::PolyPolygon
& rPolyPoly
: rCharacter
.vOutlines
)
986 tools::Rectangle
aBoundRect(rPolyPoly
.GetBoundRect());
987 double fx1
= aBoundRect
.Left() - nLeft
;
988 double fx2
= aBoundRect
.Right() - nLeft
;
990 double fM1
= fx1
/ static_cast<double>(nWidth
);
991 double fM2
= fx2
/ static_cast<double>(nWidth
);
993 GetPoint(rOutlinePoly
, vDistances
, fM1
, fx1
, fy1
);
994 GetPoint(rOutlinePoly
, vDistances
, fM2
, fx2
, fy2
);
996 double fvx
= fy2
- fy1
;
997 double fvy
= -(fx2
- fx1
);
998 fx1
= fx1
+ ((fx2
- fx1
) * 0.5);
999 fy1
= fy1
+ ((fy2
- fy1
) * 0.5);
1001 double fAngle
= atan2(-fvx
, -fvy
);
1002 double fL
= hypot(fvx
, fvy
);
1005 SAL_WARN("svx", "FitTextOutlinesToShapeOutlines div-by-zero, abandon fit");
1010 fL
= rTextArea
.aBoundRect
.GetHeight() / 2.0 + rTextArea
.aBoundRect
.Top() - rParagraph
.aBoundRect
.Center().Y();
1013 rPolyPoly
.Rotate( Point( aBoundRect
.Center().X(), rParagraph
.aBoundRect
.Center().Y() ), sin( fAngle
), cos( fAngle
) );
1014 rPolyPoly
.Move( static_cast<sal_Int32
>( ( fx1
+ fvx
)- aBoundRect
.Center().X() ), static_cast<sal_Int32
>( ( fy1
+ fvy
) - rParagraph
.aBoundRect
.Center().Y() ) );
1025 if ( ( nOutline2dIdx
+ 1 ) >= aOutlines2d
.Count() )
1027 const tools::Polygon
& rOutlinePoly( aOutlines2d
[ nOutline2dIdx
++ ] );
1028 const tools::Polygon
& rOutlinePoly2( aOutlines2d
[ nOutline2dIdx
++ ] );
1029 const sal_uInt16 nPointCount
= rOutlinePoly
.GetSize();
1030 const sal_uInt16 nPointCount2
= rOutlinePoly2
.GetSize();
1031 if ( ( nPointCount
> 1 ) && ( nPointCount2
> 1 ) )
1033 std::vector
< double > vDistances
;
1034 vDistances
.reserve( nPointCount
);
1035 std::vector
< double > vDistances2
;
1036 vDistances2
.reserve( nPointCount2
);
1037 CalcDistances( rOutlinePoly
, vDistances
);
1038 CalcDistances( rOutlinePoly2
, vDistances2
);
1039 for( auto& rParagraph
: rTextArea
.vParagraphs
)
1041 for ( auto& rCharacter
: rParagraph
.vCharacters
)
1043 for( tools::PolyPolygon
& rPolyPoly
: rCharacter
.vOutlines
)
1045 sal_uInt16 i
, nPolyCount
= rPolyPoly
.Count();
1046 for ( i
= 0; i
< nPolyCount
; i
++ )
1049 basegfx::B2DPolygon
aCandidate(rPolyPoly
[ i
].getB2DPolygon());
1051 if(aCandidate
.areControlPointsUsed())
1053 aCandidate
= basegfx::utils::adaptiveSubdivideByAngle(aCandidate
);
1056 // create local polygon copy to work on
1057 tools::Polygon
aLocalPoly(aCandidate
);
1059 InsertMissingOutlinePoints( vDistances
, rTextAreaBoundRect
, aLocalPoly
);
1060 InsertMissingOutlinePoints( vDistances2
, rTextAreaBoundRect
, aLocalPoly
);
1062 sal_uInt16 _nPointCount
= aLocalPoly
.GetSize();
1065 if (!nWidth
|| !nHeight
)
1066 throw o3tl::divide_by_zero();
1067 for (sal_uInt16 j
= 0; j
< _nPointCount
; ++j
)
1069 Point
& rPoint
= aLocalPoly
[ j
];
1070 rPoint
.AdjustX( -nLeft
);
1071 rPoint
.AdjustY( -nTop
);
1072 double fX
= static_cast<double>(rPoint
.X()) / static_cast<double>(nWidth
);
1073 double fY
= static_cast<double>(rPoint
.Y()) / static_cast<double>(nHeight
);
1075 double fx1
, fy1
, fx2
, fy2
;
1076 GetPoint( rOutlinePoly
, vDistances
, fX
, fx1
, fy1
);
1077 GetPoint( rOutlinePoly2
, vDistances2
, fX
, fx2
, fy2
);
1078 double fWidth
= fx2
- fx1
;
1079 double fHeight
= fy2
- fy1
;
1080 rPoint
.setX( static_cast<sal_Int32
>( fx1
+ fWidth
* fY
) );
1081 rPoint
.setY( static_cast<sal_Int32
>( fy1
+ fHeight
* fY
) );
1085 // write back polygon
1086 rPolyPoly
[ i
] = aLocalPoly
;
1096 static rtl::Reference
<SdrObject
> CreateSdrObjectFromParagraphOutlines(
1097 const FWData
& rFWData
,
1098 const SdrObjCustomShape
& rSdrObjCustomShape
)
1100 rtl::Reference
<SdrObject
> pRet
;
1101 basegfx::B2DPolyPolygon aPolyPoly
;
1102 if ( !rFWData
.vTextAreas
.empty() )
1104 for ( const auto& rTextArea
: rFWData
.vTextAreas
)
1106 for ( const auto& rParagraph
: rTextArea
.vParagraphs
)
1108 for ( const auto& rCharacter
: rParagraph
.vCharacters
)
1110 for( const auto& rOutline
: rCharacter
.vOutlines
)
1112 aPolyPoly
.append( rOutline
.getB2DPolyPolygon() );
1118 pRet
= new SdrPathObj(
1119 rSdrObjCustomShape
.getSdrModelFromSdrObject(),
1120 SdrObjKind::Polygon
,
1121 std::move(aPolyPoly
));
1123 SfxItemSet
aSet(rSdrObjCustomShape
.GetMergedItemSet());
1124 aSet
.ClearItem( SDRATTR_TEXTDIRECTION
); //SJ: vertical writing is not required, by removing this item no outliner is created
1125 aSet
.Put(makeSdrShadowItem(false)); // #i37011# NO shadow for FontWork geometry
1126 pRet
->SetMergedItemSet( aSet
); // * otherwise we would crash, because the outliner tries to create a Paraobject, but there is no model
1132 Reference
< i18n::XBreakIterator
> EnhancedCustomShapeFontWork::mxBreakIterator
;
1134 Reference
< i18n::XBreakIterator
> const & EnhancedCustomShapeFontWork::GetBreakIterator()
1136 if ( !mxBreakIterator
.is() )
1138 Reference
< uno::XComponentContext
> xContext
= ::comphelper::getProcessComponentContext();
1139 mxBreakIterator
= i18n::BreakIterator::create(xContext
);
1141 return mxBreakIterator
;
1144 rtl::Reference
<SdrObject
> EnhancedCustomShapeFontWork::CreateFontWork(
1145 const SdrObject
* pShape2d
,
1146 const SdrObjCustomShape
& rSdrObjCustomShape
)
1148 rtl::Reference
<SdrObject
> pRet
;
1150 // calculating scaling factor is too slow
1151 if (utl::ConfigManager::IsFuzzing())
1154 tools::PolyPolygon
aOutlines2d( GetOutlinesFromShape2d( pShape2d
) );
1155 sal_uInt16 nOutlinesCount2d
= aOutlines2d
.Count();
1156 if ( nOutlinesCount2d
)
1160 if(InitializeFontWorkData(rSdrObjCustomShape
, nOutlinesCount2d
, aFWData
))
1162 /* retrieves the horizontal scaling factor that has to be used
1163 to fit each paragraph text into its corresponding 2d outline */
1164 CalculateHorizontalScalingFactor(
1169 /* retrieving the Outlines for the each Paragraph. */
1170 if(!GetFontWorkOutline(
1172 rSdrObjCustomShape
))
1177 SdrTextHorzAdjust
eHorzAdjust(
1178 rSdrObjCustomShape
.GetMergedItem(SDRATTR_TEXT_HORZADJUST
).GetValue());
1179 bool bPPFontwork
= !rSdrObjCustomShape
.getSdrModelFromSdrObject().GetCompatibilityFlag(
1180 SdrCompatibilityFlag::LegacyFontwork
);
1181 FitTextOutlinesToShapeOutlines( aOutlines2d
, aFWData
, eHorzAdjust
, bPPFontwork
);
1183 pRet
= CreateSdrObjectFromParagraphOutlines(
1185 rSdrObjCustomShape
);
1191 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */