Bump version to 6.4-15
[LibreOffice.git] / svx / source / customshapes / EnhancedCustomShapeFontWork.cxx
blobe91751e0ed2c0c571f4d4c3e97b7346ba3981800
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/svdogrp.hxx>
24 #include <svx/svdopath.hxx>
25 #include <vcl/metric.hxx>
26 #include <svx/svdpage.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/EnhancedCustomShapeTypeNames.hxx>
39 #include <svx/svdorect.hxx>
40 #include <svx/svdoashp.hxx>
41 #include <svx/sdshitm.hxx>
42 #include <editeng/outliner.hxx>
43 #include <editeng/outlobj.hxx>
44 #include <editeng/editobj.hxx>
45 #include <editeng/editeng.hxx>
46 #include <o3tl/numeric.hxx>
47 #include <svx/svdmodel.hxx>
48 #include <vector>
49 #include <numeric>
50 #include <algorithm>
51 #include <memory>
52 #include <comphelper/processfactory.hxx>
53 #include <com/sun/star/i18n/BreakIterator.hpp>
54 #include <com/sun/star/i18n/ScriptType.hpp>
55 #include <basegfx/polygon/b2dpolypolygontools.hxx>
56 #include <com/sun/star/lang/XMultiServiceFactory.hpp>
57 #include <com/sun/star/i18n/CharacterIteratorMode.hpp>
58 #include <basegfx/polygon/b2dpolygontools.hxx>
59 #include <sal/log.hxx>
61 using namespace com::sun::star;
62 using namespace com::sun::star::uno;
64 struct FWCharacterData // representing a single character
66 std::vector< tools::PolyPolygon > vOutlines;
67 tools::Rectangle aBoundRect;
69 struct FWParagraphData // representing a single paragraph
71 OUString aString;
72 std::vector< FWCharacterData > vCharacters;
73 tools::Rectangle aBoundRect;
74 SvxFrameDirection nFrameDirection;
76 struct FWTextArea // representing multiple concluding paragraphs
78 std::vector< FWParagraphData > vParagraphs;
79 tools::Rectangle aBoundRect;
81 struct FWData // representing the whole text
83 std::vector< FWTextArea > vTextAreas;
84 double fHorizontalTextScaling;
85 double fVerticalTextScaling;
86 sal_uInt32 nMaxParagraphsPerTextArea;
87 sal_Int32 nSingleLineHeight;
88 bool bSingleLineMode;
89 bool bScaleX;
93 static bool InitializeFontWorkData(
94 const SdrObjCustomShape& rSdrObjCustomShape,
95 const sal_uInt16 nOutlinesCount2d,
96 FWData& rFWData)
98 bool bNoErr = false;
99 bool bSingleLineMode = false;
100 sal_uInt16 nTextAreaCount = nOutlinesCount2d;
101 if ( nOutlinesCount2d & 1 )
102 bSingleLineMode = true;
103 else
104 nTextAreaCount >>= 1;
106 const SdrCustomShapeGeometryItem& rGeometryItem( rSdrObjCustomShape.GetMergedItem( SDRATTR_CUSTOMSHAPE_GEOMETRY ) );
107 const css::uno::Any* pAny = rGeometryItem.GetPropertyValueByName( "TextPath", "ScaleX" );
108 if (pAny)
109 *pAny >>= rFWData.bScaleX;
110 else
111 rFWData.bScaleX = false;
113 if ( nTextAreaCount )
115 rFWData.bSingleLineMode = bSingleLineMode;
117 // setting the strings
118 OutlinerParaObject* pParaObj(rSdrObjCustomShape.GetOutlinerParaObject());
120 if ( pParaObj )
122 const EditTextObject& rTextObj = pParaObj->GetTextObject();
123 sal_Int32 nParagraphsLeft = rTextObj.GetParagraphCount();
125 rFWData.nMaxParagraphsPerTextArea = ( ( nParagraphsLeft - 1 ) / nTextAreaCount ) + 1;
126 sal_Int32 j = 0;
127 while( nParagraphsLeft && nTextAreaCount )
129 FWTextArea aTextArea;
130 sal_Int32 i, nParagraphs = ( ( nParagraphsLeft - 1 ) / nTextAreaCount ) + 1;
131 for ( i = 0; i < nParagraphs; ++i, ++j )
133 FWParagraphData aParagraphData;
134 aParagraphData.aString = rTextObj.GetText( j );
136 const SfxItemSet& rParaSet = rTextObj.GetParaAttribs( j ); // retrieving some paragraph attributes
137 aParagraphData.nFrameDirection = rParaSet.Get( EE_PARA_WRITINGDIR ).GetValue();
138 aTextArea.vParagraphs.push_back( aParagraphData );
140 rFWData.vTextAreas.push_back( aTextArea );
141 nParagraphsLeft -= nParagraphs;
142 nTextAreaCount--;
144 bNoErr = true;
147 return bNoErr;
150 static double GetLength( const tools::Polygon& rPolygon )
152 double fLength = 0;
153 if ( rPolygon.GetSize() > 1 )
155 sal_uInt16 nCount = rPolygon.GetSize();
156 while( --nCount )
157 fLength += rPolygon.CalcDistance( nCount, nCount - 1 );
159 return fLength;
163 /* CalculateHorizontalScalingFactor returns the horizontal scaling factor for
164 the whole text object, so that each text will match its corresponding 2d Outline */
165 static void CalculateHorizontalScalingFactor(
166 const SdrObjCustomShape& rSdrObjCustomShape,
167 FWData& rFWData,
168 const tools::PolyPolygon& rOutline2d)
170 double fScalingFactor = 1.0;
171 bool bScalingFactorDefined = false;
172 rFWData.fVerticalTextScaling = 1.0;
174 sal_uInt16 i = 0;
175 bool bSingleLineMode = false;
176 sal_uInt16 nOutlinesCount2d = rOutline2d.Count();
178 vcl::Font aFont;
179 const SvxFontItem& rFontItem( rSdrObjCustomShape.GetMergedItem( EE_CHAR_FONTINFO ) );
180 const SvxFontHeightItem& rFontHeight( rSdrObjCustomShape.GetMergedItem( EE_CHAR_FONTHEIGHT ) );
181 sal_Int32 nFontSize = rFontHeight.GetHeight();
183 if (rFWData.bScaleX)
184 aFont.SetFontHeight( nFontSize );
185 else
186 aFont.SetFontHeight( rSdrObjCustomShape.GetLogicRect().GetHeight() / rFWData.nMaxParagraphsPerTextArea );
188 aFont.SetAlignment( ALIGN_TOP );
189 aFont.SetFamilyName( rFontItem.GetFamilyName() );
190 aFont.SetFamily( rFontItem.GetFamily() );
191 aFont.SetStyleName( rFontItem.GetStyleName() );
192 const SvxPostureItem& rPostureItem = rSdrObjCustomShape.GetMergedItem( EE_CHAR_ITALIC );
193 aFont.SetItalic( rPostureItem.GetPosture() );
195 const SvxWeightItem& rWeightItem = rSdrObjCustomShape.GetMergedItem( EE_CHAR_WEIGHT );
196 aFont.SetWeight( rWeightItem.GetWeight() );
197 aFont.SetOrientation( 0 );
198 // initializing virtual device
200 ScopedVclPtrInstance< VirtualDevice > pVirDev(DeviceFormat::BITMASK);
201 pVirDev->SetMapMode(MapMode(MapUnit::Map100thMM));
202 pVirDev->SetFont( aFont );
204 if ( nOutlinesCount2d & 1 )
205 bSingleLineMode = true;
209 i = 0;
210 for( const auto& rTextArea : rFWData.vTextAreas )
212 // calculating the width of the corresponding 2d text area
213 double fWidth = GetLength( rOutline2d.GetObject( i++ ) );
214 if ( !bSingleLineMode )
216 fWidth += GetLength( rOutline2d.GetObject( i++ ) );
217 fWidth /= 2.0;
220 for( const auto& rParagraph : rTextArea.vParagraphs )
222 double fTextWidth = pVirDev->GetTextWidth( rParagraph.aString );
223 if ( fTextWidth > 0.0 )
225 double fScale = fWidth / fTextWidth;
226 if ( !bScalingFactorDefined )
228 fScalingFactor = fScale;
229 bScalingFactorDefined = true;
231 else if ( fScale < fScalingFactor || ( rFWData.bScaleX && fScalingFactor < 1.0 ) )
233 fScalingFactor = fScale;
239 if (fScalingFactor < 1.0)
241 nFontSize--;
242 aFont.SetFontHeight( nFontSize );
243 pVirDev->SetFont( aFont );
246 while (rFWData.bScaleX && fScalingFactor < 1.0 && nFontSize > 1 );
248 if (nFontSize > 1)
249 rFWData.fVerticalTextScaling = static_cast<double>(nFontSize) / rFontHeight.GetHeight();
250 // Add some padding
251 if (rFWData.bScaleX)
252 fScalingFactor *= 1.1;
254 rFWData.fHorizontalTextScaling = fScalingFactor;
257 static void GetTextAreaOutline(
258 const FWData& rFWData,
259 const SdrObjCustomShape& rSdrObjCustomShape,
260 FWTextArea& rTextArea,
261 bool bSameLetterHeights)
263 bool bIsVertical(rSdrObjCustomShape.IsVerticalWriting());
264 sal_Int32 nVerticalOffset = rFWData.nMaxParagraphsPerTextArea > rTextArea.vParagraphs.size()
265 ? rFWData.nSingleLineHeight / 2 : 0;
267 for( auto& rParagraph : rTextArea.vParagraphs )
269 const OUString& rText = rParagraph.aString;
270 if ( !rText.isEmpty() )
272 // generating vcl/font
273 sal_uInt16 nScriptType = i18n::ScriptType::LATIN;
274 Reference< i18n::XBreakIterator > xBI( EnhancedCustomShapeFontWork::GetBreakIterator() );
275 if ( xBI.is() )
277 nScriptType = xBI->getScriptType( rText, 0 );
278 if( i18n::ScriptType::WEAK == nScriptType )
280 sal_Int32 nChg = xBI->endOfScript( rText, 0, nScriptType );
281 if (nChg < rText.getLength() && nChg >= 0)
282 nScriptType = xBI->getScriptType( rText, nChg );
283 else
284 nScriptType = i18n::ScriptType::LATIN;
287 sal_uInt16 nFntItm = EE_CHAR_FONTINFO;
288 if ( nScriptType == i18n::ScriptType::COMPLEX )
289 nFntItm = EE_CHAR_FONTINFO_CTL;
290 else if ( nScriptType == i18n::ScriptType::ASIAN )
291 nFntItm = EE_CHAR_FONTINFO_CJK;
292 const SvxFontItem& rFontItem = static_cast<const SvxFontItem&>(rSdrObjCustomShape.GetMergedItem( nFntItm ));
293 vcl::Font aFont;
295 aFont.SetFontHeight( rFWData.nSingleLineHeight );
297 aFont.SetAlignment( ALIGN_TOP );
299 aFont.SetFamilyName( rFontItem.GetFamilyName() );
300 aFont.SetFamily( rFontItem.GetFamily() );
301 aFont.SetStyleName( rFontItem.GetStyleName() );
302 aFont.SetOrientation( 0 );
304 const SvxPostureItem& rPostureItem = rSdrObjCustomShape.GetMergedItem( EE_CHAR_ITALIC );
305 aFont.SetItalic( rPostureItem.GetPosture() );
307 const SvxWeightItem& rWeightItem = rSdrObjCustomShape.GetMergedItem( EE_CHAR_WEIGHT );
308 aFont.SetWeight( rWeightItem.GetWeight() );
310 // initializing virtual device
311 ScopedVclPtrInstance< VirtualDevice > pVirDev(DeviceFormat::BITMASK);
312 pVirDev->SetMapMode(MapMode(MapUnit::Map100thMM));
313 pVirDev->SetFont( aFont );
314 pVirDev->EnableRTL();
315 if ( rParagraph.nFrameDirection == SvxFrameDirection::Horizontal_RL_TB )
316 pVirDev->SetLayoutMode( ComplexTextLayoutFlags::BiDiRtl );
318 const SvxCharScaleWidthItem& rCharScaleWidthItem = rSdrObjCustomShape.GetMergedItem( EE_CHAR_FONTWIDTH );
319 sal_uInt16 nCharScaleWidth = rCharScaleWidthItem.GetValue();
320 std::unique_ptr<long[]> pDXArry;
321 sal_Int32 nWidth = 0;
323 // VERTICAL
324 if ( bIsVertical )
326 // vertical _> each single character needs to be rotated by 90
327 sal_Int32 i;
328 sal_Int32 nHeight = 0;
329 tools::Rectangle aSingleCharacterUnion;
330 for ( i = 0; i < rText.getLength(); i++ )
332 FWCharacterData aCharacterData;
333 OUString aCharText( rText[ i ] );
334 if ( pVirDev->GetTextOutlines( aCharacterData.vOutlines, aCharText, 0, 0, -1, nWidth, pDXArry.get() ) )
336 sal_Int32 nTextWidth = pVirDev->GetTextWidth( aCharText);
337 if ( aCharacterData.vOutlines.empty() )
339 nHeight += rFWData.nSingleLineHeight;
341 else
343 for ( auto& rOutline : aCharacterData.vOutlines )
345 // rotating
346 rOutline.Rotate( Point( nTextWidth / 2, rFWData.nSingleLineHeight / 2 ), 900 );
347 aCharacterData.aBoundRect.Union( rOutline.GetBoundRect() );
349 for ( auto& rOutline : aCharacterData.vOutlines )
351 sal_Int32 nM = - aCharacterData.aBoundRect.Left() + nHeight;
352 rOutline.Move( nM, 0 );
353 aCharacterData.aBoundRect.Move( nM, 0 );
355 nHeight += aCharacterData.aBoundRect.GetWidth() + ( rFWData.nSingleLineHeight / 5 );
356 aSingleCharacterUnion.Union( aCharacterData.aBoundRect );
359 rParagraph.vCharacters.push_back( aCharacterData );
361 for ( auto& rCharacter : rParagraph.vCharacters )
363 for ( auto& rOutline : rCharacter.vOutlines )
365 rOutline.Move( ( aSingleCharacterUnion.GetWidth() - rCharacter.aBoundRect.GetWidth() ) / 2, 0 );
369 else
371 if ( ( nCharScaleWidth != 100 ) && nCharScaleWidth )
372 { // applying character spacing
373 pDXArry.reset(new long[ rText.getLength() ]);
374 pVirDev->GetTextArray( rText, pDXArry.get());
375 FontMetric aFontMetric( pVirDev->GetFontMetric() );
376 aFont.SetAverageFontWidth( static_cast<sal_Int32>( static_cast<double>(aFontMetric.GetAverageFontWidth()) * ( double(100) / static_cast<double>(nCharScaleWidth) ) ) );
377 pVirDev->SetFont( aFont );
379 FWCharacterData aCharacterData;
380 if ( pVirDev->GetTextOutlines( aCharacterData.vOutlines, rText, 0, 0, -1, nWidth, pDXArry.get() ) )
382 rParagraph.vCharacters.push_back( aCharacterData );
386 // vertical alignment
387 for ( auto& rCharacter : rParagraph.vCharacters )
389 for( tools::PolyPolygon& rPolyPoly : rCharacter.vOutlines )
391 if ( nVerticalOffset )
392 rPolyPoly.Move( 0, nVerticalOffset );
394 // retrieving the boundrect for the paragraph
395 tools::Rectangle aBoundRect( rPolyPoly.GetBoundRect() );
396 rParagraph.aBoundRect.Union( aBoundRect );
400 // updating the boundrect for the text area by merging the current paragraph boundrect
401 if ( rParagraph.aBoundRect.IsEmpty() )
403 if ( rTextArea.aBoundRect.IsEmpty() )
404 rTextArea.aBoundRect = tools::Rectangle( Point( 0, 0 ), Size( 1, rFWData.nSingleLineHeight ) );
405 else
406 rTextArea.aBoundRect.AdjustBottom(rFWData.nSingleLineHeight );
408 else
410 tools::Rectangle& rParagraphBoundRect = rParagraph.aBoundRect;
411 rTextArea.aBoundRect.Union( rParagraphBoundRect );
413 if ( bSameLetterHeights )
415 for ( auto& rCharacter : rParagraph.vCharacters )
417 for( auto& rOutline : rCharacter.vOutlines )
419 tools::Rectangle aPolyPolyBoundRect( rOutline.GetBoundRect() );
420 if (aPolyPolyBoundRect.GetHeight() != rParagraphBoundRect.GetHeight() && aPolyPolyBoundRect.GetHeight())
421 rOutline.Scale( 1.0, static_cast<double>(rParagraphBoundRect.GetHeight()) / aPolyPolyBoundRect.GetHeight() );
422 aPolyPolyBoundRect = rOutline.GetBoundRect();
423 sal_Int32 nMove = aPolyPolyBoundRect.Top() - rParagraphBoundRect.Top();
424 if ( nMove )
425 rOutline.Move( 0, -nMove );
430 if ( bIsVertical )
431 nVerticalOffset -= rFWData.nSingleLineHeight;
432 else
433 nVerticalOffset += rFWData.nSingleLineHeight;
437 static bool GetFontWorkOutline(
438 FWData& rFWData,
439 const SdrObjCustomShape& rSdrObjCustomShape)
441 SdrTextHorzAdjust eHorzAdjust(rSdrObjCustomShape.GetMergedItem( SDRATTR_TEXT_HORZADJUST ).GetValue());
442 drawing::TextFitToSizeType const eFTS(rSdrObjCustomShape.GetMergedItem( SDRATTR_TEXT_FITTOSIZE ).GetValue());
444 bool bSameLetterHeights = false;
445 const SdrCustomShapeGeometryItem& rGeometryItem(rSdrObjCustomShape.GetMergedItem( SDRATTR_CUSTOMSHAPE_GEOMETRY ));
446 const css::uno::Any* pAny = rGeometryItem.GetPropertyValueByName( "TextPath", "SameLetterHeights" );
447 if ( pAny )
448 *pAny >>= bSameLetterHeights;
450 const SvxFontHeightItem& rFontHeight( rSdrObjCustomShape.GetMergedItem( EE_CHAR_FONTHEIGHT ) );
451 if (rFWData.bScaleX)
452 rFWData.nSingleLineHeight = rFWData.fVerticalTextScaling * rFontHeight.GetHeight();
453 else
454 rFWData.nSingleLineHeight = static_cast<sal_Int32>( ( static_cast<double>( rSdrObjCustomShape.GetLogicRect().GetHeight() )
455 / rFWData.nMaxParagraphsPerTextArea ) * rFWData.fHorizontalTextScaling );
457 if (rFWData.nSingleLineHeight == SAL_MIN_INT32)
458 return false;
460 for ( auto& rTextArea : rFWData.vTextAreas )
462 GetTextAreaOutline(
463 rFWData,
464 rSdrObjCustomShape,
465 rTextArea,
466 bSameLetterHeights);
468 if (eFTS == drawing::TextFitToSizeType_ALLLINES ||
469 // tdf#97630 interpret PROPORTIONAL same as ALLLINES so we don't
470 // need another ODF attribute!
471 eFTS == drawing::TextFitToSizeType_PROPORTIONAL)
473 for ( auto& rParagraph : rTextArea.vParagraphs )
475 sal_Int32 nParaWidth = rParagraph.aBoundRect.GetWidth();
476 if ( nParaWidth )
478 double fScale = static_cast<double>(rTextArea.aBoundRect.GetWidth()) / nParaWidth;
480 for ( auto& rCharacter : rParagraph.vCharacters )
482 for( auto& rOutline : rCharacter.vOutlines )
484 rOutline.Scale( fScale, 1.0 );
490 else if (rFWData.bScaleX)
492 const SdrTextVertAdjust nVertJustify = rSdrObjCustomShape.GetMergedItem( SDRATTR_TEXT_VERTADJUST ).GetValue();
493 double fFactor = nVertJustify == SdrTextVertAdjust::SDRTEXTVERTADJUST_BOTTOM ? -0.5 : ( nVertJustify == SdrTextVertAdjust::SDRTEXTVERTADJUST_TOP ? 0.5 : 0 );
495 for ( auto& rParagraph : rTextArea.vParagraphs )
497 sal_Int32 nHorzDiff = 0;
498 sal_Int32 nVertDiff = static_cast<double>( rFWData.nSingleLineHeight ) * fFactor * ( rTextArea.vParagraphs.size() - 1 );
500 if ( eHorzAdjust == SDRTEXTHORZADJUST_CENTER )
501 nHorzDiff = ( rFWData.fHorizontalTextScaling * rTextArea.aBoundRect.GetWidth() - rParagraph.aBoundRect.GetWidth() ) / 2;
502 else if ( eHorzAdjust == SDRTEXTHORZADJUST_RIGHT )
503 nHorzDiff = ( rFWData.fHorizontalTextScaling * rTextArea.aBoundRect.GetWidth() - rParagraph.aBoundRect.GetWidth() );
505 if (nHorzDiff)
507 for ( auto& rCharacter : rParagraph.vCharacters )
509 for( auto& rOutline : rCharacter.vOutlines )
511 rOutline.Move( nHorzDiff, nVertDiff );
517 else
519 switch( eHorzAdjust )
521 case SDRTEXTHORZADJUST_RIGHT :
522 case SDRTEXTHORZADJUST_CENTER:
524 for ( auto& rParagraph : rTextArea.vParagraphs )
526 sal_Int32 nHorzDiff = 0;
527 if ( eHorzAdjust == SDRTEXTHORZADJUST_CENTER )
528 nHorzDiff = ( rTextArea.aBoundRect.GetWidth() - rParagraph.aBoundRect.GetWidth() ) / 2;
529 else if ( eHorzAdjust == SDRTEXTHORZADJUST_RIGHT )
530 nHorzDiff = ( rTextArea.aBoundRect.GetWidth() - rParagraph.aBoundRect.GetWidth() );
531 if ( nHorzDiff )
533 for ( auto& rCharacter : rParagraph.vCharacters )
535 for( auto& rOutline : rCharacter.vOutlines )
537 rOutline.Move( nHorzDiff, 0 );
543 break;
544 default:
545 case SDRTEXTHORZADJUST_BLOCK : break; // don't know
546 case SDRTEXTHORZADJUST_LEFT : break; // already left aligned -> nothing to do
551 return true;
554 static basegfx::B2DPolyPolygon GetOutlinesFromShape2d( const SdrObject* pShape2d )
556 basegfx::B2DPolyPolygon aOutlines2d;
558 SdrObjListIter aObjListIter( *pShape2d, SdrIterMode::DeepWithGroups );
559 while( aObjListIter.IsMore() )
561 SdrObject* pPartObj = aObjListIter.Next();
562 if ( dynamic_cast<const SdrPathObj*>( pPartObj) != nullptr )
564 basegfx::B2DPolyPolygon aCandidate(static_cast<SdrPathObj*>(pPartObj)->GetPathPoly());
565 if(aCandidate.areControlPointsUsed())
567 aCandidate = basegfx::utils::adaptiveSubdivideByAngle(aCandidate);
569 aOutlines2d.append(aCandidate);
573 return aOutlines2d;
576 static void CalcDistances( const tools::Polygon& rPoly, std::vector< double >& rDistances )
578 sal_uInt16 i, nCount = rPoly.GetSize();
579 if ( nCount > 1 )
581 for ( i = 0; i < nCount; i++ )
583 double fDistance = i ? rPoly.CalcDistance( i, i - 1 ) : 0.0;
584 rDistances.push_back( fDistance );
586 std::partial_sum( rDistances.begin(), rDistances.end(), rDistances.begin() );
587 double fLength = rDistances[ rDistances.size() - 1 ];
588 if ( fLength > 0.0 )
590 for ( auto& rDistance : rDistances )
591 rDistance /= fLength;
596 static void InsertMissingOutlinePoints( const std::vector< double >& rDistances,
597 const tools::Rectangle& rTextAreaBoundRect, tools::Polygon& rPoly )
599 sal_uInt16 nSize = rPoly.GetSize();
600 if (nSize == 0)
601 return;
603 long nTextWidth = rTextAreaBoundRect.GetWidth();
605 if (nTextWidth == 0)
606 throw o3tl::divide_by_zero();
608 double fLastDistance = 0.0;
609 for (sal_uInt16 i = 0; i < nSize; ++i)
611 Point& rPoint = rPoly[ i ];
612 double fDistance = static_cast<double>( rPoint.X() - rTextAreaBoundRect.Left() ) / static_cast<double>(nTextWidth);
613 if ( i )
615 if ( fDistance > fLastDistance )
617 std::vector< double >::const_iterator aIter = std::upper_bound( rDistances.begin(), rDistances.end(), fLastDistance );
618 if ( aIter != rDistances.end() && ( *aIter > fLastDistance ) && ( *aIter < fDistance ) )
620 Point& rPt0 = rPoly[ i - 1 ];
621 sal_Int32 fX = rPoint.X() - rPt0.X();
622 sal_Int32 fY = rPoint.Y() - rPt0.Y();
623 double fd = ( 1.0 / ( fDistance - fLastDistance ) ) * ( *aIter - fLastDistance );
624 rPoly.Insert( i, Point( static_cast<sal_Int32>( rPt0.X() + fX * fd ), static_cast<sal_Int32>( rPt0.Y() + fY * fd ) ) );
625 fDistance = *aIter;
628 else if ( fDistance < fLastDistance )
630 std::vector< double >::const_iterator aIter = std::lower_bound( rDistances.begin(), rDistances.end(), fLastDistance );
631 if ( aIter != rDistances.begin() )
633 --aIter;
634 if ( ( *aIter > fDistance ) && ( *aIter < fLastDistance ) )
636 Point& rPt0 = rPoly[ i - 1 ];
637 sal_Int32 fX = rPoint.X() - rPt0.X();
638 sal_Int32 fY = rPoint.Y() - rPt0.Y();
639 double fd = ( 1.0 / ( fDistance - fLastDistance ) ) * ( *aIter - fLastDistance );
640 rPoly.Insert( i, Point( static_cast<sal_Int32>( rPt0.X() + fX * fd ), static_cast<sal_Int32>( rPt0.Y() + fY * fd ) ) );
641 fDistance = *aIter;
646 fLastDistance = fDistance;
650 static void GetPoint( const tools::Polygon& rPoly, const std::vector< double >& rDistances, const double& fX, double& fx1, double& fy1 )
652 fy1 = fx1 = 0.0;
653 if ( rPoly.GetSize() > 1 )
655 std::vector< double >::const_iterator aIter = std::lower_bound( rDistances.begin(), rDistances.end(), fX );
656 sal_uInt16 nIdx = sal::static_int_cast<sal_uInt16>( std::distance( rDistances.begin(), aIter ) );
657 if ( aIter == rDistances.end() )
658 nIdx--;
659 const Point& rPt = rPoly[ nIdx ];
660 fx1 = rPt.X();
661 fy1 = rPt.Y();
662 if ( nIdx && ( aIter != rDistances.end() ) && !rtl::math::approxEqual( *aIter, fX ) )
664 nIdx = sal::static_int_cast<sal_uInt16>( std::distance( rDistances.begin(), aIter ) );
665 double fDist0 = *( aIter - 1 );
666 double fd = ( 1.0 / ( *aIter - fDist0 ) ) * ( fX - fDist0 );
667 const Point& rPt2 = rPoly[ nIdx - 1 ];
668 double fWidth = rPt.X() - rPt2.X();
669 double fHeight= rPt.Y() - rPt2.Y();
670 fWidth *= fd;
671 fHeight*= fd;
672 fx1 = rPt2.X() + fWidth;
673 fy1 = rPt2.Y() + fHeight;
678 static void FitTextOutlinesToShapeOutlines( const tools::PolyPolygon& aOutlines2d, FWData& rFWData )
680 sal_uInt16 nOutline2dIdx = 0;
681 for( auto& rTextArea : rFWData.vTextAreas )
683 tools::Rectangle rTextAreaBoundRect = rTextArea.aBoundRect;
684 sal_Int32 nLeft = rTextAreaBoundRect.Left();
685 sal_Int32 nTop = rTextAreaBoundRect.Top();
686 sal_Int32 nWidth = rTextAreaBoundRect.GetWidth();
687 sal_Int32 nHeight= rTextAreaBoundRect.GetHeight();
689 if (rFWData.bScaleX)
691 nWidth *= rFWData.fHorizontalTextScaling;
694 if ( rFWData.bSingleLineMode && nHeight && nWidth )
696 if ( nOutline2dIdx >= aOutlines2d.Count() )
697 break;
698 const tools::Polygon& rOutlinePoly( aOutlines2d[ nOutline2dIdx++ ] );
699 const sal_uInt16 nPointCount = rOutlinePoly.GetSize();
700 if ( nPointCount > 1 )
702 std::vector< double > vDistances;
703 vDistances.reserve( nPointCount );
704 CalcDistances( rOutlinePoly, vDistances );
705 if ( !vDistances.empty() )
707 for( auto& rParagraph : rTextArea.vParagraphs )
709 for ( auto& rCharacter : rParagraph.vCharacters )
711 for( tools::PolyPolygon& rPolyPoly : rCharacter.vOutlines )
713 tools::Rectangle aBoundRect( rPolyPoly.GetBoundRect() );
714 double fx1 = aBoundRect.Left() - nLeft;
715 double fx2 = aBoundRect.Right() - nLeft;
716 double fy1, fy2;
717 double fM1 = fx1 / static_cast<double>(nWidth);
718 double fM2 = fx2 / static_cast<double>(nWidth);
720 GetPoint( rOutlinePoly, vDistances, fM1, fx1, fy1 );
721 GetPoint( rOutlinePoly, vDistances, fM2, fx2, fy2 );
723 double fvx = fy2 - fy1;
724 double fvy = - ( fx2 - fx1 );
725 fx1 = fx1 + ( ( fx2 - fx1 ) * 0.5 );
726 fy1 = fy1 + ( ( fy2 - fy1 ) * 0.5 );
728 double fAngle = atan2( -fvx, -fvy );
729 double fL = hypot( fvx, fvy );
730 if (fL == 0.0)
732 SAL_WARN("svx", "FitTextOutlinesToShapeOutlines div-by-zero, abandon fit");
733 break;
735 fvx = fvx / fL;
736 fvy = fvy / fL;
737 fL = rTextArea.aBoundRect.GetHeight() / 2.0 + rTextArea.aBoundRect.Top() - rParagraph.aBoundRect.Center().Y();
738 fvx *= fL;
739 fvy *= fL;
740 rPolyPoly.Rotate( Point( aBoundRect.Center().X(), rParagraph.aBoundRect.Center().Y() ), sin( fAngle ), cos( fAngle ) );
741 rPolyPoly.Move( static_cast<sal_Int32>( ( fx1 + fvx )- aBoundRect.Center().X() ), static_cast<sal_Int32>( ( fy1 + fvy ) - rParagraph.aBoundRect.Center().Y() ) );
748 else
750 if ( ( nOutline2dIdx + 1 ) >= aOutlines2d.Count() )
751 break;
752 const tools::Polygon& rOutlinePoly( aOutlines2d[ nOutline2dIdx++ ] );
753 const tools::Polygon& rOutlinePoly2( aOutlines2d[ nOutline2dIdx++ ] );
754 const sal_uInt16 nPointCount = rOutlinePoly.GetSize();
755 const sal_uInt16 nPointCount2 = rOutlinePoly2.GetSize();
756 if ( ( nPointCount > 1 ) && ( nPointCount2 > 1 ) )
758 std::vector< double > vDistances;
759 vDistances.reserve( nPointCount );
760 std::vector< double > vDistances2;
761 vDistances2.reserve( nPointCount2 );
762 CalcDistances( rOutlinePoly, vDistances );
763 CalcDistances( rOutlinePoly2, vDistances2 );
764 for( auto& rParagraph : rTextArea.vParagraphs )
766 for ( auto& rCharacter : rParagraph.vCharacters )
768 for( tools::PolyPolygon& rPolyPoly : rCharacter.vOutlines )
770 sal_uInt16 i, nPolyCount = rPolyPoly.Count();
771 for ( i = 0; i < nPolyCount; i++ )
773 // #i35928#
774 basegfx::B2DPolygon aCandidate(rPolyPoly[ i ].getB2DPolygon());
776 if(aCandidate.areControlPointsUsed())
778 aCandidate = basegfx::utils::adaptiveSubdivideByAngle(aCandidate);
781 // create local polygon copy to work on
782 tools::Polygon aLocalPoly(aCandidate);
784 InsertMissingOutlinePoints( vDistances, rTextAreaBoundRect, aLocalPoly );
785 InsertMissingOutlinePoints( vDistances2, rTextAreaBoundRect, aLocalPoly );
787 sal_uInt16 _nPointCount = aLocalPoly.GetSize();
788 if (_nPointCount)
790 if (!nWidth || !nHeight)
791 throw o3tl::divide_by_zero();
792 for (sal_uInt16 j = 0; j < _nPointCount; ++j)
794 Point& rPoint = aLocalPoly[ j ];
795 rPoint.AdjustX( -nLeft );
796 rPoint.AdjustY( -nTop );
797 double fX = static_cast<double>(rPoint.X()) / static_cast<double>(nWidth);
798 double fY = static_cast<double>(rPoint.Y()) / static_cast<double>(nHeight);
800 double fx1, fy1, fx2, fy2;
801 GetPoint( rOutlinePoly, vDistances, fX, fx1, fy1 );
802 GetPoint( rOutlinePoly2, vDistances2, fX, fx2, fy2 );
803 double fWidth = fx2 - fx1;
804 double fHeight= fy2 - fy1;
805 rPoint.setX( static_cast<sal_Int32>( fx1 + fWidth * fY ) );
806 rPoint.setY( static_cast<sal_Int32>( fy1 + fHeight* fY ) );
810 // write back polygon
811 rPolyPoly[ i ] = aLocalPoly;
821 static SdrObject* CreateSdrObjectFromParagraphOutlines(
822 const FWData& rFWData,
823 const SdrObjCustomShape& rSdrObjCustomShape)
825 SdrObject* pRet = nullptr;
826 basegfx::B2DPolyPolygon aPolyPoly;
827 if ( !rFWData.vTextAreas.empty() )
829 for ( const auto& rTextArea : rFWData.vTextAreas )
831 for ( const auto& rParagraph : rTextArea.vParagraphs )
833 for ( const auto& rCharacter : rParagraph.vCharacters )
835 for( const auto& rOutline : rCharacter.vOutlines )
837 aPolyPoly.append( rOutline.getB2DPolyPolygon() );
843 pRet = new SdrPathObj(
844 rSdrObjCustomShape.getSdrModelFromSdrObject(),
845 OBJ_POLY,
846 aPolyPoly);
848 SfxItemSet aSet(rSdrObjCustomShape.GetMergedItemSet());
849 aSet.ClearItem( SDRATTR_TEXTDIRECTION ); //SJ: vertical writing is not required, by removing this item no outliner is created
850 aSet.Put(makeSdrShadowItem(false)); // #i37011# NO shadow for FontWork geometry
851 pRet->SetMergedItemSet( aSet ); // * otherwise we would crash, because the outliner tries to create a Paraobject, but there is no model
854 return pRet;
857 Reference < i18n::XBreakIterator > EnhancedCustomShapeFontWork::mxBreakIterator;
859 Reference < i18n::XBreakIterator > const & EnhancedCustomShapeFontWork::GetBreakIterator()
861 if ( !mxBreakIterator.is() )
863 Reference< uno::XComponentContext > xContext = ::comphelper::getProcessComponentContext();
864 mxBreakIterator = i18n::BreakIterator::create(xContext);
866 return mxBreakIterator;
869 SdrObject* EnhancedCustomShapeFontWork::CreateFontWork(
870 const SdrObject* pShape2d,
871 const SdrObjCustomShape& rSdrObjCustomShape)
873 SdrObject* pRet = nullptr;
875 tools::PolyPolygon aOutlines2d( GetOutlinesFromShape2d( pShape2d ) );
876 sal_uInt16 nOutlinesCount2d = aOutlines2d.Count();
877 if ( nOutlinesCount2d )
879 FWData aFWData;
881 if(InitializeFontWorkData(rSdrObjCustomShape, nOutlinesCount2d, aFWData))
883 /* retrieves the horizontal scaling factor that has to be used
884 to fit each paragraph text into its corresponding 2d outline */
885 CalculateHorizontalScalingFactor(
886 rSdrObjCustomShape,
887 aFWData,
888 aOutlines2d);
890 /* retrieving the Outlines for the each Paragraph. */
891 if(!GetFontWorkOutline(
892 aFWData,
893 rSdrObjCustomShape))
895 return nullptr;
898 FitTextOutlinesToShapeOutlines( aOutlines2d, aFWData );
900 pRet = CreateSdrObjectFromParagraphOutlines(
901 aFWData,
902 rSdrObjCustomShape);
905 return pRet;
908 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */