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