bump product version to 7.2.5.1
[LibreOffice.git] / svx / source / customshapes / EnhancedCustomShapeFontWork.cxx
blob3ad4287c7743dbd752225ab0bd87783c03aefe39
1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 /*
3 * This file is part of the LibreOffice project.
5 * This Source Code Form is subject to the terms of the Mozilla Public
6 * License, v. 2.0. If a copy of the MPL was not distributed with this
7 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
9 * This file incorporates work covered by the following license notice:
11 * Licensed to the Apache Software Foundation (ASF) under one or more
12 * contributor license agreements. See the NOTICE file distributed
13 * with this work for additional information regarding copyright
14 * ownership. The ASF licenses this file to you under the Apache
15 * License, Version 2.0 (the "License"); you may not use this file
16 * except in compliance with the License. You may obtain a copy of
17 * the License at http://www.apache.org/licenses/LICENSE-2.0 .
20 #include "EnhancedCustomShapeFontWork.hxx"
21 #include <svl/itemset.hxx>
22 #include <svx/svddef.hxx>
23 #include <svx/svdopath.hxx>
24 #include <vcl/metric.hxx>
25 #include <svx/sdasitm.hxx>
26 #include <svx/sdtfsitm.hxx>
27 #include <vcl/virdev.hxx>
28 #include <svx/svditer.hxx>
29 #include <editeng/eeitem.hxx>
30 #include <editeng/frmdiritem.hxx>
31 #include <editeng/fontitem.hxx>
32 #include <editeng/postitem.hxx>
33 #include <editeng/wghtitem.hxx>
34 #include <editeng/fhgtitem.hxx>
35 #include <editeng/charscaleitem.hxx>
36 #include <svx/svdoashp.hxx>
37 #include <svx/sdshitm.hxx>
38 #include <editeng/outlobj.hxx>
39 #include <editeng/editobj.hxx>
40 #include <o3tl/numeric.hxx>
41 #include <vector>
42 #include <numeric>
43 #include <algorithm>
44 #include <memory>
45 #include <comphelper/processfactory.hxx>
46 #include <com/sun/star/i18n/BreakIterator.hpp>
47 #include <com/sun/star/i18n/ScriptType.hpp>
48 #include <basegfx/polygon/b2dpolypolygontools.hxx>
49 #include <basegfx/polygon/b2dpolygontools.hxx>
50 #include <sal/log.hxx>
51 #include <rtl/math.hxx>
53 using namespace com::sun::star;
54 using namespace com::sun::star::uno;
56 namespace {
58 struct FWCharacterData // representing a single character
60 std::vector< tools::PolyPolygon > vOutlines;
61 tools::Rectangle aBoundRect;
63 struct FWParagraphData // representing a single paragraph
65 OUString aString;
66 std::vector< FWCharacterData > vCharacters;
67 tools::Rectangle aBoundRect;
68 SvxFrameDirection nFrameDirection;
70 struct FWTextArea // representing multiple concluding paragraphs
72 std::vector< FWParagraphData > vParagraphs;
73 tools::Rectangle aBoundRect;
75 struct FWData // representing the whole text
77 std::vector< FWTextArea > vTextAreas;
78 double fHorizontalTextScaling;
79 double fVerticalTextScaling;
80 sal_uInt32 nMaxParagraphsPerTextArea;
81 sal_Int32 nSingleLineHeight;
82 bool bSingleLineMode;
83 bool bScaleX;
88 static bool InitializeFontWorkData(
89 const SdrObjCustomShape& rSdrObjCustomShape,
90 const sal_uInt16 nOutlinesCount2d,
91 FWData& rFWData)
93 bool bNoErr = false;
94 bool bSingleLineMode = false;
95 sal_uInt16 nTextAreaCount = nOutlinesCount2d;
96 if ( nOutlinesCount2d & 1 )
97 bSingleLineMode = true;
98 else
99 nTextAreaCount >>= 1;
101 const SdrCustomShapeGeometryItem& rGeometryItem( rSdrObjCustomShape.GetMergedItem( SDRATTR_CUSTOMSHAPE_GEOMETRY ) );
102 const css::uno::Any* pAny = rGeometryItem.GetPropertyValueByName( "TextPath", "ScaleX" );
103 if (pAny)
104 *pAny >>= rFWData.bScaleX;
105 else
106 rFWData.bScaleX = false;
108 if ( nTextAreaCount )
110 rFWData.bSingleLineMode = bSingleLineMode;
112 // setting the strings
113 OutlinerParaObject* pParaObj(rSdrObjCustomShape.GetOutlinerParaObject());
115 if ( pParaObj )
117 const EditTextObject& rTextObj = pParaObj->GetTextObject();
118 sal_Int32 nParagraphsLeft = rTextObj.GetParagraphCount();
120 rFWData.nMaxParagraphsPerTextArea = ( ( nParagraphsLeft - 1 ) / nTextAreaCount ) + 1;
121 sal_Int32 j = 0;
122 while( nParagraphsLeft && nTextAreaCount )
124 FWTextArea aTextArea;
125 sal_Int32 i, nParagraphs = ( ( nParagraphsLeft - 1 ) / nTextAreaCount ) + 1;
126 for ( i = 0; i < nParagraphs; ++i, ++j )
128 FWParagraphData aParagraphData;
129 aParagraphData.aString = rTextObj.GetText( j );
131 const SfxItemSet& rParaSet = rTextObj.GetParaAttribs( j ); // retrieving some paragraph attributes
132 aParagraphData.nFrameDirection = rParaSet.Get( EE_PARA_WRITINGDIR ).GetValue();
133 aTextArea.vParagraphs.push_back( aParagraphData );
135 rFWData.vTextAreas.push_back( aTextArea );
136 nParagraphsLeft -= nParagraphs;
137 nTextAreaCount--;
139 bNoErr = true;
142 return bNoErr;
145 static double GetLength( const tools::Polygon& rPolygon )
147 double fLength = 0;
148 if ( rPolygon.GetSize() > 1 )
150 sal_uInt16 nCount = rPolygon.GetSize();
151 while( --nCount )
152 fLength += rPolygon.CalcDistance( nCount, nCount - 1 );
154 return fLength;
158 /* CalculateHorizontalScalingFactor returns the horizontal scaling factor for
159 the whole text object, so that each text will match its corresponding 2d Outline */
160 static void CalculateHorizontalScalingFactor(
161 const SdrObjCustomShape& rSdrObjCustomShape,
162 FWData& rFWData,
163 const tools::PolyPolygon& rOutline2d)
165 double fScalingFactor = 1.0;
166 rFWData.fVerticalTextScaling = 1.0;
168 sal_uInt16 i = 0;
169 bool bSingleLineMode = false;
170 sal_uInt16 nOutlinesCount2d = rOutline2d.Count();
172 vcl::Font aFont;
173 const SvxFontItem& rFontItem( rSdrObjCustomShape.GetMergedItem( EE_CHAR_FONTINFO ) );
174 const SvxFontHeightItem& rFontHeight( rSdrObjCustomShape.GetMergedItem( EE_CHAR_FONTHEIGHT ) );
175 sal_Int32 nFontSize = rFontHeight.GetHeight();
177 if (rFWData.bScaleX)
178 aFont.SetFontHeight( nFontSize );
179 else
180 aFont.SetFontHeight( rSdrObjCustomShape.GetLogicRect().GetHeight() / rFWData.nMaxParagraphsPerTextArea );
182 aFont.SetAlignment( ALIGN_TOP );
183 aFont.SetFamilyName( rFontItem.GetFamilyName() );
184 aFont.SetFamily( rFontItem.GetFamily() );
185 aFont.SetStyleName( rFontItem.GetStyleName() );
186 const SvxPostureItem& rPostureItem = rSdrObjCustomShape.GetMergedItem( EE_CHAR_ITALIC );
187 aFont.SetItalic( rPostureItem.GetPosture() );
189 const SvxWeightItem& rWeightItem = rSdrObjCustomShape.GetMergedItem( EE_CHAR_WEIGHT );
190 aFont.SetWeight( rWeightItem.GetWeight() );
191 aFont.SetOrientation( 0_deg10 );
192 // initializing virtual device
194 ScopedVclPtrInstance< VirtualDevice > pVirDev(DeviceFormat::DEFAULT);
195 pVirDev->SetMapMode(MapMode(MapUnit::Map100thMM));
196 pVirDev->SetFont( aFont );
198 if ( nOutlinesCount2d & 1 )
199 bSingleLineMode = true;
201 // In case of rFWData.bScaleX == true it loops with reduced font size until the current run
202 // results in a fScalingFactor >=1.0. The fact, that case rFWData.bScaleX == true keeps font
203 // size if possible, is not done here with scaling factor 1 but is done in method
204 // FitTextOutlinesToShapeOutlines()
207 i = 0;
208 bool bScalingFactorDefined = false; // New calculation for each font size
209 for( const auto& rTextArea : rFWData.vTextAreas )
211 // calculating the width of the corresponding 2d text area
212 double fWidth = GetLength( rOutline2d.GetObject( i++ ) );
213 if ( !bSingleLineMode )
215 fWidth += GetLength( rOutline2d.GetObject( i++ ) );
216 fWidth /= 2.0;
219 for( const auto& rParagraph : rTextArea.vParagraphs )
221 double fTextWidth = pVirDev->GetTextWidth( rParagraph.aString );
222 if ( fTextWidth > 0.0 )
224 double fScale = fWidth / fTextWidth;
225 if ( !bScalingFactorDefined )
227 fScalingFactor = fScale;
228 bScalingFactorDefined = true;
230 else if (fScale < fScalingFactor)
232 fScalingFactor = fScale;
238 if (fScalingFactor < 1.0)
240 nFontSize--;
241 aFont.SetFontHeight( nFontSize );
242 pVirDev->SetFont( aFont );
245 while (rFWData.bScaleX && fScalingFactor < 1.0 && nFontSize > 1 );
247 if (nFontSize > 1)
248 rFWData.fVerticalTextScaling = static_cast<double>(nFontSize) / rFontHeight.GetHeight();
250 rFWData.fHorizontalTextScaling = fScalingFactor;
253 static void GetTextAreaOutline(
254 const FWData& rFWData,
255 const SdrObjCustomShape& rSdrObjCustomShape,
256 FWTextArea& rTextArea,
257 bool bSameLetterHeights)
259 bool bIsVertical(rSdrObjCustomShape.IsVerticalWriting());
260 sal_Int32 nVerticalOffset = rFWData.nMaxParagraphsPerTextArea > rTextArea.vParagraphs.size()
261 ? rFWData.nSingleLineHeight / 2 : 0;
263 for( auto& rParagraph : rTextArea.vParagraphs )
265 const OUString& rText = rParagraph.aString;
266 if ( !rText.isEmpty() )
268 // generating vcl/font
269 sal_uInt16 nScriptType = i18n::ScriptType::LATIN;
270 Reference< i18n::XBreakIterator > xBI( EnhancedCustomShapeFontWork::GetBreakIterator() );
271 if ( xBI.is() )
273 nScriptType = xBI->getScriptType( rText, 0 );
274 if( i18n::ScriptType::WEAK == nScriptType )
276 sal_Int32 nChg = xBI->endOfScript( rText, 0, nScriptType );
277 if (nChg < rText.getLength() && nChg >= 0)
278 nScriptType = xBI->getScriptType( rText, nChg );
279 else
280 nScriptType = i18n::ScriptType::LATIN;
283 sal_uInt16 nFntItm = EE_CHAR_FONTINFO;
284 if ( nScriptType == i18n::ScriptType::COMPLEX )
285 nFntItm = EE_CHAR_FONTINFO_CTL;
286 else if ( nScriptType == i18n::ScriptType::ASIAN )
287 nFntItm = EE_CHAR_FONTINFO_CJK;
288 const SvxFontItem& rFontItem = static_cast<const SvxFontItem&>(rSdrObjCustomShape.GetMergedItem( nFntItm ));
289 vcl::Font aFont;
291 aFont.SetFontHeight( rFWData.nSingleLineHeight );
293 aFont.SetAlignment( ALIGN_TOP );
295 aFont.SetFamilyName( rFontItem.GetFamilyName() );
296 aFont.SetFamily( rFontItem.GetFamily() );
297 aFont.SetStyleName( rFontItem.GetStyleName() );
298 aFont.SetOrientation( 0_deg10 );
300 const SvxPostureItem& rPostureItem = rSdrObjCustomShape.GetMergedItem( EE_CHAR_ITALIC );
301 aFont.SetItalic( rPostureItem.GetPosture() );
303 const SvxWeightItem& rWeightItem = rSdrObjCustomShape.GetMergedItem( EE_CHAR_WEIGHT );
304 aFont.SetWeight( rWeightItem.GetWeight() );
306 // initializing virtual device
307 ScopedVclPtrInstance< VirtualDevice > pVirDev(DeviceFormat::DEFAULT);
308 pVirDev->SetMapMode(MapMode(MapUnit::Map100thMM));
309 pVirDev->SetFont( aFont );
310 pVirDev->EnableRTL();
311 if ( rParagraph.nFrameDirection == SvxFrameDirection::Horizontal_RL_TB )
312 pVirDev->SetLayoutMode( ComplexTextLayoutFlags::BiDiRtl );
314 const SvxCharScaleWidthItem& rCharScaleWidthItem = rSdrObjCustomShape.GetMergedItem( EE_CHAR_FONTWIDTH );
315 sal_uInt16 nCharScaleWidth = rCharScaleWidthItem.GetValue();
316 std::unique_ptr<tools::Long[]> pDXArry;
317 sal_Int32 nWidth = 0;
319 // VERTICAL
320 if ( bIsVertical )
322 // vertical _> each single character needs to be rotated by 90
323 sal_Int32 i;
324 sal_Int32 nHeight = 0;
325 tools::Rectangle aSingleCharacterUnion;
326 for ( i = 0; i < rText.getLength(); i++ )
328 FWCharacterData aCharacterData;
329 OUString aCharText( rText[ i ] );
330 if ( pVirDev->GetTextOutlines( aCharacterData.vOutlines, aCharText, 0, 0, -1, nWidth, pDXArry.get() ) )
332 sal_Int32 nTextWidth = pVirDev->GetTextWidth( aCharText);
333 if ( aCharacterData.vOutlines.empty() )
335 nHeight += rFWData.nSingleLineHeight;
337 else
339 for ( auto& rOutline : aCharacterData.vOutlines )
341 // rotating
342 rOutline.Rotate( Point( nTextWidth / 2, rFWData.nSingleLineHeight / 2 ), 900_deg10 );
343 aCharacterData.aBoundRect.Union( rOutline.GetBoundRect() );
345 for ( auto& rOutline : aCharacterData.vOutlines )
347 sal_Int32 nM = - aCharacterData.aBoundRect.Left() + nHeight;
348 rOutline.Move( nM, 0 );
349 aCharacterData.aBoundRect.Move( nM, 0 );
351 nHeight += aCharacterData.aBoundRect.GetWidth() + ( rFWData.nSingleLineHeight / 5 );
352 aSingleCharacterUnion.Union( aCharacterData.aBoundRect );
355 rParagraph.vCharacters.push_back( aCharacterData );
357 for ( auto& rCharacter : rParagraph.vCharacters )
359 for ( auto& rOutline : rCharacter.vOutlines )
361 rOutline.Move( ( aSingleCharacterUnion.GetWidth() - rCharacter.aBoundRect.GetWidth() ) / 2, 0 );
365 else
367 if ( ( nCharScaleWidth != 100 ) && nCharScaleWidth )
368 { // applying character spacing
369 pDXArry.reset(new tools::Long[ rText.getLength() ]);
370 pVirDev->GetTextArray( rText, pDXArry.get());
371 FontMetric aFontMetric( pVirDev->GetFontMetric() );
372 aFont.SetAverageFontWidth( static_cast<sal_Int32>( static_cast<double>(aFontMetric.GetAverageFontWidth()) * ( double(100) / static_cast<double>(nCharScaleWidth) ) ) );
373 pVirDev->SetFont( aFont );
375 FWCharacterData aCharacterData;
376 if ( pVirDev->GetTextOutlines( aCharacterData.vOutlines, rText, 0, 0, -1, nWidth, pDXArry.get() ) )
378 rParagraph.vCharacters.push_back( aCharacterData );
382 // vertical alignment
383 for ( auto& rCharacter : rParagraph.vCharacters )
385 for( tools::PolyPolygon& rPolyPoly : rCharacter.vOutlines )
387 if ( nVerticalOffset )
388 rPolyPoly.Move( 0, nVerticalOffset );
390 // retrieving the boundrect for the paragraph
391 tools::Rectangle aBoundRect( rPolyPoly.GetBoundRect() );
392 rParagraph.aBoundRect.Union( aBoundRect );
396 // updating the boundrect for the text area by merging the current paragraph boundrect
397 if ( rParagraph.aBoundRect.IsEmpty() )
399 if ( rTextArea.aBoundRect.IsEmpty() )
400 rTextArea.aBoundRect = tools::Rectangle( Point( 0, 0 ), Size( 1, rFWData.nSingleLineHeight ) );
401 else
402 rTextArea.aBoundRect.AdjustBottom(rFWData.nSingleLineHeight );
404 else
406 tools::Rectangle& rParagraphBoundRect = rParagraph.aBoundRect;
407 rTextArea.aBoundRect.Union( rParagraphBoundRect );
409 if ( bSameLetterHeights )
411 for ( auto& rCharacter : rParagraph.vCharacters )
413 for( auto& rOutline : rCharacter.vOutlines )
415 tools::Rectangle aPolyPolyBoundRect( rOutline.GetBoundRect() );
416 if (aPolyPolyBoundRect.GetHeight() != rParagraphBoundRect.GetHeight() && aPolyPolyBoundRect.GetHeight())
417 rOutline.Scale( 1.0, static_cast<double>(rParagraphBoundRect.GetHeight()) / aPolyPolyBoundRect.GetHeight() );
418 aPolyPolyBoundRect = rOutline.GetBoundRect();
419 sal_Int32 nMove = aPolyPolyBoundRect.Top() - rParagraphBoundRect.Top();
420 if ( nMove )
421 rOutline.Move( 0, -nMove );
426 if ( bIsVertical )
427 nVerticalOffset -= rFWData.nSingleLineHeight;
428 else
429 nVerticalOffset += rFWData.nSingleLineHeight;
433 static bool GetFontWorkOutline(
434 FWData& rFWData,
435 const SdrObjCustomShape& rSdrObjCustomShape)
437 SdrTextHorzAdjust eHorzAdjust(rSdrObjCustomShape.GetMergedItem( SDRATTR_TEXT_HORZADJUST ).GetValue());
438 drawing::TextFitToSizeType const eFTS(rSdrObjCustomShape.GetMergedItem( SDRATTR_TEXT_FITTOSIZE ).GetValue());
440 bool bSameLetterHeights = false;
441 const SdrCustomShapeGeometryItem& rGeometryItem(rSdrObjCustomShape.GetMergedItem( SDRATTR_CUSTOMSHAPE_GEOMETRY ));
442 const css::uno::Any* pAny = rGeometryItem.GetPropertyValueByName( "TextPath", "SameLetterHeights" );
443 if ( pAny )
444 *pAny >>= bSameLetterHeights;
446 const SvxFontHeightItem& rFontHeight( rSdrObjCustomShape.GetMergedItem( EE_CHAR_FONTHEIGHT ) );
447 if (rFWData.bScaleX)
448 rFWData.nSingleLineHeight = rFWData.fVerticalTextScaling * rFontHeight.GetHeight();
449 else
450 rFWData.nSingleLineHeight = static_cast<sal_Int32>( ( static_cast<double>( rSdrObjCustomShape.GetLogicRect().GetHeight() )
451 / rFWData.nMaxParagraphsPerTextArea ) * rFWData.fHorizontalTextScaling );
453 if (rFWData.nSingleLineHeight == SAL_MIN_INT32)
454 return false;
456 for ( auto& rTextArea : rFWData.vTextAreas )
458 GetTextAreaOutline(
459 rFWData,
460 rSdrObjCustomShape,
461 rTextArea,
462 bSameLetterHeights);
464 if (eFTS == drawing::TextFitToSizeType_ALLLINES ||
465 // tdf#97630 interpret PROPORTIONAL same as ALLLINES so we don't
466 // need another ODF attribute!
467 eFTS == drawing::TextFitToSizeType_PROPORTIONAL)
469 for ( auto& rParagraph : rTextArea.vParagraphs )
471 sal_Int32 nParaWidth = rParagraph.aBoundRect.GetWidth();
472 if ( nParaWidth )
474 double fScale = static_cast<double>(rTextArea.aBoundRect.GetWidth()) / nParaWidth;
476 for ( auto& rCharacter : rParagraph.vCharacters )
478 for( auto& rOutline : rCharacter.vOutlines )
480 rOutline.Scale( fScale, 1.0 );
486 else if (rFWData.bScaleX)
488 const SdrTextVertAdjust nVertJustify = rSdrObjCustomShape.GetMergedItem( SDRATTR_TEXT_VERTADJUST ).GetValue();
489 double fFactor = nVertJustify == SdrTextVertAdjust::SDRTEXTVERTADJUST_BOTTOM ? -0.5 : ( nVertJustify == SdrTextVertAdjust::SDRTEXTVERTADJUST_TOP ? 0.5 : 0 );
491 for ( auto& rParagraph : rTextArea.vParagraphs )
493 sal_Int32 nHorzDiff = 0;
494 sal_Int32 nVertDiff = static_cast<double>( rFWData.nSingleLineHeight ) * fFactor * ( rTextArea.vParagraphs.size() - 1 );
496 if ( eHorzAdjust == SDRTEXTHORZADJUST_CENTER )
497 nHorzDiff = ( rFWData.fHorizontalTextScaling * rTextArea.aBoundRect.GetWidth() - rParagraph.aBoundRect.GetWidth() ) / 2;
498 else if ( eHorzAdjust == SDRTEXTHORZADJUST_RIGHT )
499 nHorzDiff = ( rFWData.fHorizontalTextScaling * rTextArea.aBoundRect.GetWidth() - rParagraph.aBoundRect.GetWidth() );
501 if (nHorzDiff || nVertDiff)
503 for ( auto& rCharacter : rParagraph.vCharacters )
505 for( auto& rOutline : rCharacter.vOutlines )
507 rOutline.Move( nHorzDiff, nVertDiff );
513 else
515 switch( eHorzAdjust )
517 case SDRTEXTHORZADJUST_RIGHT :
518 case SDRTEXTHORZADJUST_CENTER:
520 for ( auto& rParagraph : rTextArea.vParagraphs )
522 sal_Int32 nHorzDiff = 0;
523 if ( eHorzAdjust == SDRTEXTHORZADJUST_CENTER )
524 nHorzDiff = ( rTextArea.aBoundRect.GetWidth() - rParagraph.aBoundRect.GetWidth() ) / 2;
525 else if ( eHorzAdjust == SDRTEXTHORZADJUST_RIGHT )
526 nHorzDiff = ( rTextArea.aBoundRect.GetWidth() - rParagraph.aBoundRect.GetWidth() );
527 if ( nHorzDiff )
529 for ( auto& rCharacter : rParagraph.vCharacters )
531 for( auto& rOutline : rCharacter.vOutlines )
533 rOutline.Move( nHorzDiff, 0 );
539 break;
540 default:
541 case SDRTEXTHORZADJUST_BLOCK : break; // don't know
542 case SDRTEXTHORZADJUST_LEFT : break; // already left aligned -> nothing to do
547 return true;
550 static basegfx::B2DPolyPolygon GetOutlinesFromShape2d( const SdrObject* pShape2d )
552 basegfx::B2DPolyPolygon aOutlines2d;
554 SdrObjListIter aObjListIter( *pShape2d, SdrIterMode::DeepWithGroups );
555 while( aObjListIter.IsMore() )
557 SdrObject* pPartObj = aObjListIter.Next();
558 if ( auto pPathObj = dynamic_cast<const SdrPathObj*>( pPartObj))
560 basegfx::B2DPolyPolygon aCandidate(pPathObj->GetPathPoly());
561 if(aCandidate.areControlPointsUsed())
563 aCandidate = basegfx::utils::adaptiveSubdivideByAngle(aCandidate);
565 aOutlines2d.append(aCandidate);
569 return aOutlines2d;
572 static void CalcDistances( const tools::Polygon& rPoly, std::vector< double >& rDistances )
574 sal_uInt16 i, nCount = rPoly.GetSize();
575 if ( nCount <= 1 )
576 return;
578 for ( i = 0; i < nCount; i++ )
580 double fDistance = i ? rPoly.CalcDistance( i, i - 1 ) : 0.0;
581 rDistances.push_back( fDistance );
583 std::partial_sum( rDistances.begin(), rDistances.end(), rDistances.begin() );
584 double fLength = rDistances[ rDistances.size() - 1 ];
585 if ( fLength > 0.0 )
587 for ( auto& rDistance : rDistances )
588 rDistance /= fLength;
592 static void InsertMissingOutlinePoints( const std::vector< double >& rDistances,
593 const tools::Rectangle& rTextAreaBoundRect, tools::Polygon& rPoly )
595 sal_uInt16 nSize = rPoly.GetSize();
596 if (nSize == 0)
597 return;
599 tools::Long nTextWidth = rTextAreaBoundRect.GetWidth();
601 if (nTextWidth == 0)
602 throw o3tl::divide_by_zero();
604 double fLastDistance = 0.0;
605 for (sal_uInt16 i = 0; i < nSize; ++i)
607 Point& rPoint = rPoly[ i ];
608 double fDistance = static_cast<double>( rPoint.X() - rTextAreaBoundRect.Left() ) / static_cast<double>(nTextWidth);
609 if ( i )
611 if ( fDistance > fLastDistance )
613 std::vector< double >::const_iterator aIter = std::upper_bound( rDistances.begin(), rDistances.end(), fLastDistance );
614 if ( aIter != rDistances.end() && ( *aIter > fLastDistance ) && ( *aIter < fDistance ) )
616 Point& rPt0 = rPoly[ i - 1 ];
617 sal_Int32 fX = rPoint.X() - rPt0.X();
618 sal_Int32 fY = rPoint.Y() - rPt0.Y();
619 double fd = ( 1.0 / ( fDistance - fLastDistance ) ) * ( *aIter - fLastDistance );
620 rPoly.Insert( i, Point( static_cast<sal_Int32>( rPt0.X() + fX * fd ), static_cast<sal_Int32>( rPt0.Y() + fY * fd ) ) );
621 fDistance = *aIter;
624 else if ( fDistance < fLastDistance )
626 std::vector< double >::const_iterator aIter = std::lower_bound( rDistances.begin(), rDistances.end(), fLastDistance );
627 if ( aIter != rDistances.begin() )
629 --aIter;
630 if ( ( *aIter > fDistance ) && ( *aIter < fLastDistance ) )
632 Point& rPt0 = rPoly[ i - 1 ];
633 sal_Int32 fX = rPoint.X() - rPt0.X();
634 sal_Int32 fY = rPoint.Y() - rPt0.Y();
635 double fd = ( 1.0 / ( fDistance - fLastDistance ) ) * ( *aIter - fLastDistance );
636 rPoly.Insert( i, Point( static_cast<sal_Int32>( rPt0.X() + fX * fd ), static_cast<sal_Int32>( rPt0.Y() + fY * fd ) ) );
637 fDistance = *aIter;
642 fLastDistance = fDistance;
646 static void GetPoint( const tools::Polygon& rPoly, const std::vector< double >& rDistances, const double& fX, double& fx1, double& fy1 )
648 fy1 = fx1 = 0.0;
649 if ( rPoly.GetSize() <= 1 )
650 return;
652 std::vector< double >::const_iterator aIter = std::lower_bound( rDistances.begin(), rDistances.end(), fX );
653 sal_uInt16 nIdx = sal::static_int_cast<sal_uInt16>( std::distance( rDistances.begin(), aIter ) );
654 if ( aIter == rDistances.end() )
655 nIdx--;
656 const Point& rPt = rPoly[ nIdx ];
657 fx1 = rPt.X();
658 fy1 = rPt.Y();
659 if ( !nIdx || ( aIter == rDistances.end() ) || rtl::math::approxEqual( *aIter, fX ) )
660 return;
662 nIdx = sal::static_int_cast<sal_uInt16>( std::distance( rDistances.begin(), aIter ) );
663 double fDist0 = *( aIter - 1 );
664 double fd = ( 1.0 / ( *aIter - fDist0 ) ) * ( fX - fDist0 );
665 const Point& rPt2 = rPoly[ nIdx - 1 ];
666 double fWidth = rPt.X() - rPt2.X();
667 double fHeight= rPt.Y() - rPt2.Y();
668 fWidth *= fd;
669 fHeight*= fd;
670 fx1 = rPt2.X() + fWidth;
671 fy1 = rPt2.Y() + fHeight;
674 static void FitTextOutlinesToShapeOutlines( const tools::PolyPolygon& aOutlines2d, FWData& rFWData )
676 sal_uInt16 nOutline2dIdx = 0;
677 for( auto& rTextArea : rFWData.vTextAreas )
679 tools::Rectangle rTextAreaBoundRect = rTextArea.aBoundRect;
680 sal_Int32 nLeft = rTextAreaBoundRect.Left();
681 sal_Int32 nTop = rTextAreaBoundRect.Top();
682 sal_Int32 nWidth = rTextAreaBoundRect.GetWidth();
683 sal_Int32 nHeight= rTextAreaBoundRect.GetHeight();
685 if (rFWData.bScaleX)
687 nWidth *= rFWData.fHorizontalTextScaling;
690 if ( rFWData.bSingleLineMode && nHeight && nWidth )
692 if ( nOutline2dIdx >= aOutlines2d.Count() )
693 break;
694 const tools::Polygon& rOutlinePoly( aOutlines2d[ nOutline2dIdx++ ] );
695 const sal_uInt16 nPointCount = rOutlinePoly.GetSize();
696 if ( nPointCount > 1 )
698 std::vector< double > vDistances;
699 vDistances.reserve( nPointCount );
700 CalcDistances( rOutlinePoly, vDistances );
701 if ( !vDistances.empty() )
703 for( auto& rParagraph : rTextArea.vParagraphs )
705 for ( auto& rCharacter : rParagraph.vCharacters )
707 for( tools::PolyPolygon& rPolyPoly : rCharacter.vOutlines )
709 tools::Rectangle aBoundRect( rPolyPoly.GetBoundRect() );
710 double fx1 = aBoundRect.Left() - nLeft;
711 double fx2 = aBoundRect.Right() - nLeft;
712 double fy1, fy2;
713 double fM1 = fx1 / static_cast<double>(nWidth);
714 double fM2 = fx2 / static_cast<double>(nWidth);
716 GetPoint( rOutlinePoly, vDistances, fM1, fx1, fy1 );
717 GetPoint( rOutlinePoly, vDistances, fM2, fx2, fy2 );
719 double fvx = fy2 - fy1;
720 double fvy = - ( fx2 - fx1 );
721 fx1 = fx1 + ( ( fx2 - fx1 ) * 0.5 );
722 fy1 = fy1 + ( ( fy2 - fy1 ) * 0.5 );
724 double fAngle = atan2( -fvx, -fvy );
725 double fL = hypot( fvx, fvy );
726 if (fL == 0.0)
728 SAL_WARN("svx", "FitTextOutlinesToShapeOutlines div-by-zero, abandon fit");
729 break;
731 fvx = fvx / fL;
732 fvy = fvy / fL;
733 fL = rTextArea.aBoundRect.GetHeight() / 2.0 + rTextArea.aBoundRect.Top() - rParagraph.aBoundRect.Center().Y();
734 fvx *= fL;
735 fvy *= fL;
736 rPolyPoly.Rotate( Point( aBoundRect.Center().X(), rParagraph.aBoundRect.Center().Y() ), sin( fAngle ), cos( fAngle ) );
737 rPolyPoly.Move( static_cast<sal_Int32>( ( fx1 + fvx )- aBoundRect.Center().X() ), static_cast<sal_Int32>( ( fy1 + fvy ) - rParagraph.aBoundRect.Center().Y() ) );
744 else
746 if ( ( nOutline2dIdx + 1 ) >= aOutlines2d.Count() )
747 break;
748 const tools::Polygon& rOutlinePoly( aOutlines2d[ nOutline2dIdx++ ] );
749 const tools::Polygon& rOutlinePoly2( aOutlines2d[ nOutline2dIdx++ ] );
750 const sal_uInt16 nPointCount = rOutlinePoly.GetSize();
751 const sal_uInt16 nPointCount2 = rOutlinePoly2.GetSize();
752 if ( ( nPointCount > 1 ) && ( nPointCount2 > 1 ) )
754 std::vector< double > vDistances;
755 vDistances.reserve( nPointCount );
756 std::vector< double > vDistances2;
757 vDistances2.reserve( nPointCount2 );
758 CalcDistances( rOutlinePoly, vDistances );
759 CalcDistances( rOutlinePoly2, vDistances2 );
760 for( auto& rParagraph : rTextArea.vParagraphs )
762 for ( auto& rCharacter : rParagraph.vCharacters )
764 for( tools::PolyPolygon& rPolyPoly : rCharacter.vOutlines )
766 sal_uInt16 i, nPolyCount = rPolyPoly.Count();
767 for ( i = 0; i < nPolyCount; i++ )
769 // #i35928#
770 basegfx::B2DPolygon aCandidate(rPolyPoly[ i ].getB2DPolygon());
772 if(aCandidate.areControlPointsUsed())
774 aCandidate = basegfx::utils::adaptiveSubdivideByAngle(aCandidate);
777 // create local polygon copy to work on
778 tools::Polygon aLocalPoly(aCandidate);
780 InsertMissingOutlinePoints( vDistances, rTextAreaBoundRect, aLocalPoly );
781 InsertMissingOutlinePoints( vDistances2, rTextAreaBoundRect, aLocalPoly );
783 sal_uInt16 _nPointCount = aLocalPoly.GetSize();
784 if (_nPointCount)
786 if (!nWidth || !nHeight)
787 throw o3tl::divide_by_zero();
788 for (sal_uInt16 j = 0; j < _nPointCount; ++j)
790 Point& rPoint = aLocalPoly[ j ];
791 rPoint.AdjustX( -nLeft );
792 rPoint.AdjustY( -nTop );
793 double fX = static_cast<double>(rPoint.X()) / static_cast<double>(nWidth);
794 double fY = static_cast<double>(rPoint.Y()) / static_cast<double>(nHeight);
796 double fx1, fy1, fx2, fy2;
797 GetPoint( rOutlinePoly, vDistances, fX, fx1, fy1 );
798 GetPoint( rOutlinePoly2, vDistances2, fX, fx2, fy2 );
799 double fWidth = fx2 - fx1;
800 double fHeight= fy2 - fy1;
801 rPoint.setX( static_cast<sal_Int32>( fx1 + fWidth * fY ) );
802 rPoint.setY( static_cast<sal_Int32>( fy1 + fHeight* fY ) );
806 // write back polygon
807 rPolyPoly[ i ] = aLocalPoly;
817 static SdrObject* CreateSdrObjectFromParagraphOutlines(
818 const FWData& rFWData,
819 const SdrObjCustomShape& rSdrObjCustomShape)
821 SdrObject* pRet = nullptr;
822 basegfx::B2DPolyPolygon aPolyPoly;
823 if ( !rFWData.vTextAreas.empty() )
825 for ( const auto& rTextArea : rFWData.vTextAreas )
827 for ( const auto& rParagraph : rTextArea.vParagraphs )
829 for ( const auto& rCharacter : rParagraph.vCharacters )
831 for( const auto& rOutline : rCharacter.vOutlines )
833 aPolyPoly.append( rOutline.getB2DPolyPolygon() );
839 pRet = new SdrPathObj(
840 rSdrObjCustomShape.getSdrModelFromSdrObject(),
841 OBJ_POLY,
842 aPolyPoly);
844 SfxItemSet aSet(rSdrObjCustomShape.GetMergedItemSet());
845 aSet.ClearItem( SDRATTR_TEXTDIRECTION ); //SJ: vertical writing is not required, by removing this item no outliner is created
846 aSet.Put(makeSdrShadowItem(false)); // #i37011# NO shadow for FontWork geometry
847 pRet->SetMergedItemSet( aSet ); // * otherwise we would crash, because the outliner tries to create a Paraobject, but there is no model
850 return pRet;
853 Reference < i18n::XBreakIterator > EnhancedCustomShapeFontWork::mxBreakIterator;
855 Reference < i18n::XBreakIterator > const & EnhancedCustomShapeFontWork::GetBreakIterator()
857 if ( !mxBreakIterator.is() )
859 Reference< uno::XComponentContext > xContext = ::comphelper::getProcessComponentContext();
860 mxBreakIterator = i18n::BreakIterator::create(xContext);
862 return mxBreakIterator;
865 SdrObject* EnhancedCustomShapeFontWork::CreateFontWork(
866 const SdrObject* pShape2d,
867 const SdrObjCustomShape& rSdrObjCustomShape)
869 SdrObject* pRet = nullptr;
871 tools::PolyPolygon aOutlines2d( GetOutlinesFromShape2d( pShape2d ) );
872 sal_uInt16 nOutlinesCount2d = aOutlines2d.Count();
873 if ( nOutlinesCount2d )
875 FWData aFWData;
877 if(InitializeFontWorkData(rSdrObjCustomShape, nOutlinesCount2d, aFWData))
879 /* retrieves the horizontal scaling factor that has to be used
880 to fit each paragraph text into its corresponding 2d outline */
881 CalculateHorizontalScalingFactor(
882 rSdrObjCustomShape,
883 aFWData,
884 aOutlines2d);
886 /* retrieving the Outlines for the each Paragraph. */
887 if(!GetFontWorkOutline(
888 aFWData,
889 rSdrObjCustomShape))
891 return nullptr;
894 FitTextOutlinesToShapeOutlines( aOutlines2d, aFWData );
896 pRet = CreateSdrObjectFromParagraphOutlines(
897 aFWData,
898 rSdrObjCustomShape);
901 return pRet;
904 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */