Version 7.6.3.2-android, tag libreoffice-7.6.3.2-android
[LibreOffice.git] / svx / source / customshapes / EnhancedCustomShape2d.cxx
blob148fe9e07cfeedf880c11a2e805619f569f7d03d
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 <sal/config.h>
22 #include <o3tl/string_view.hxx>
23 #include <svx/EnhancedCustomShape2d.hxx>
24 #include <svx/EnhancedCustomShapeGeometry.hxx>
25 #include <svx/EnhancedCustomShapeTypeNames.hxx>
26 #include <svx/svdoashp.hxx>
27 #include <svx/svdtrans.hxx>
28 #include <svx/svdogrp.hxx>
29 #include <svx/svdopath.hxx>
30 #include <svx/svdorect.hxx>
31 #include <svx/svdpage.hxx>
32 #include <svx/xflclit.hxx>
33 #include <svx/xfillit0.hxx>
34 #include <svx/xlineit0.hxx>
35 #include <svx/xlnstit.hxx>
36 #include <svx/xlnedit.hxx>
37 #include <svx/xlnstwit.hxx>
38 #include <svx/xlnedwit.hxx>
39 #include <svx/xlnstcit.hxx>
40 #include <svx/xlnedcit.hxx>
41 #include <svx/xflgrit.hxx>
42 #include <svx/xflhtit.hxx>
43 #include <svx/xbtmpit.hxx>
44 #include <svx/xhatch.hxx>
45 #include <svx/sdshitm.hxx>
46 #include <com/sun/star/awt/Size.hpp>
47 #include <com/sun/star/drawing/EnhancedCustomShapeParameterType.hpp>
48 #include <com/sun/star/drawing/EnhancedCustomShapeSegmentCommand.hpp>
49 #include <com/sun/star/drawing/EnhancedCustomShapeAdjustmentValue.hpp>
50 #include <com/sun/star/drawing/EnhancedCustomShapeSegment.hpp>
51 #include <com/sun/star/drawing/EnhancedCustomShapeTextFrame.hpp>
52 #include <basegfx/numeric/ftools.hxx>
53 #include <basegfx/color/bcolortools.hxx>
54 #include <basegfx/polygon/b2dpolygon.hxx>
55 #include <basegfx/polygon/b2dpolygontools.hxx>
56 #include <basegfx/matrix/b2dhommatrixtools.hxx>
57 #include <sal/log.hxx>
59 #include <algorithm>
60 #include <cstdlib>
61 #include <string_view>
62 #include <unordered_set>
64 using namespace ::com::sun::star;
65 using namespace ::com::sun::star::uno;
66 using namespace ::com::sun::star::drawing;
67 using namespace ::com::sun::star::drawing::EnhancedCustomShapeSegmentCommand;
69 void EnhancedCustomShape2d::SetEnhancedCustomShapeParameter( EnhancedCustomShapeParameter& rParameter, const sal_Int32 nValue )
71 sal_uInt32 nDat = static_cast<sal_uInt32>(nValue);
72 sal_Int32 nNewValue = nValue;
74 // check if this is a special point
75 if ( ( nDat >> 16 ) == 0x8000 )
77 nNewValue = static_cast<sal_uInt16>(nDat);
78 rParameter.Type = EnhancedCustomShapeParameterType::EQUATION;
80 else
81 rParameter.Type = EnhancedCustomShapeParameterType::NORMAL;
82 rParameter.Value <<= nNewValue;
85 OUString EnhancedCustomShape2d::GetEquation( const sal_uInt16 nFlags, sal_Int32 nP1, sal_Int32 nP2, sal_Int32 nP3 )
87 OUString aEquation;
88 bool b1Special = ( nFlags & 0x2000 ) != 0;
89 bool b2Special = ( nFlags & 0x4000 ) != 0;
90 bool b3Special = ( nFlags & 0x8000 ) != 0;
91 switch( nFlags & 0xff )
93 case 0 :
94 case 14 :
96 sal_Int32 nOptimize = 0;
97 if ( nP1 )
98 nOptimize |= 1;
99 if ( nP2 )
100 nOptimize |= 2;
101 if ( b1Special )
102 nOptimize |= 4;
103 if ( b2Special )
104 nOptimize |= 8;
105 switch( nOptimize )
107 case 0 :
108 break;
109 case 1 :
110 case 4 :
111 case 5 :
112 EnhancedCustomShape2d::AppendEnhancedCustomShapeEquationParameter( aEquation, nP1, b1Special );
113 break;
114 case 2 :
115 case 8 :
116 case 10:
117 EnhancedCustomShape2d::AppendEnhancedCustomShapeEquationParameter( aEquation, nP2, b2Special );
118 break;
119 default :
121 EnhancedCustomShape2d::AppendEnhancedCustomShapeEquationParameter( aEquation, nP1, b1Special );
122 aEquation += "+";
123 EnhancedCustomShape2d::AppendEnhancedCustomShapeEquationParameter( aEquation, nP2, b2Special );
125 break;
127 if ( b3Special || nP3 )
129 aEquation += "-";
130 EnhancedCustomShape2d::AppendEnhancedCustomShapeEquationParameter( aEquation, nP3, b3Special );
133 break;
134 case 1 :
136 EnhancedCustomShape2d::AppendEnhancedCustomShapeEquationParameter( aEquation, nP1, b1Special );
137 if ( b2Special || ( nP2 != 1 ) )
139 aEquation += "*";
140 EnhancedCustomShape2d::AppendEnhancedCustomShapeEquationParameter( aEquation, nP2, b2Special );
142 if ( b3Special || ( ( nP3 != 1 ) && ( nP3 != 0 ) ) )
144 aEquation += "/";
145 EnhancedCustomShape2d::AppendEnhancedCustomShapeEquationParameter( aEquation, nP3, b3Special );
148 break;
149 case 2 :
151 aEquation += "(";
152 EnhancedCustomShape2d::AppendEnhancedCustomShapeEquationParameter( aEquation, nP1, b1Special );
153 aEquation += "+";
154 EnhancedCustomShape2d::AppendEnhancedCustomShapeEquationParameter( aEquation, nP2, b2Special );
155 aEquation += ")/2";
157 break;
158 case 3 :
160 aEquation += "abs(";
161 EnhancedCustomShape2d::AppendEnhancedCustomShapeEquationParameter( aEquation, nP1, b1Special );
162 aEquation += ")";
164 break;
165 case 4 :
167 aEquation += "min(";
168 EnhancedCustomShape2d::AppendEnhancedCustomShapeEquationParameter( aEquation, nP1, b1Special );
169 aEquation += ",";
170 EnhancedCustomShape2d::AppendEnhancedCustomShapeEquationParameter( aEquation, nP2, b2Special );
171 aEquation += ")";
173 break;
174 case 5 :
176 aEquation += "max(";
177 EnhancedCustomShape2d::AppendEnhancedCustomShapeEquationParameter( aEquation, nP1, b1Special );
178 aEquation += ",";
179 EnhancedCustomShape2d::AppendEnhancedCustomShapeEquationParameter( aEquation, nP2, b2Special );
180 aEquation += ")";
182 break;
183 case 6 :
185 aEquation += "if(";
186 EnhancedCustomShape2d::AppendEnhancedCustomShapeEquationParameter( aEquation, nP1, b1Special );
187 aEquation += ",";
188 EnhancedCustomShape2d::AppendEnhancedCustomShapeEquationParameter( aEquation, nP2, b2Special );
189 aEquation += ",";
190 EnhancedCustomShape2d::AppendEnhancedCustomShapeEquationParameter( aEquation, nP3, b3Special );
191 aEquation += ")";
193 break;
194 case 7 :
196 aEquation += "sqrt(";
197 EnhancedCustomShape2d::AppendEnhancedCustomShapeEquationParameter( aEquation, nP1, b1Special );
198 aEquation += "*";
199 EnhancedCustomShape2d::AppendEnhancedCustomShapeEquationParameter( aEquation, nP1, b1Special );
200 aEquation += "+";
201 EnhancedCustomShape2d::AppendEnhancedCustomShapeEquationParameter( aEquation, nP2, b2Special );
202 aEquation += "*";
203 EnhancedCustomShape2d::AppendEnhancedCustomShapeEquationParameter( aEquation, nP2, b2Special );
204 aEquation += "+";
205 EnhancedCustomShape2d::AppendEnhancedCustomShapeEquationParameter( aEquation, nP3, b3Special );
206 aEquation += "*";
207 EnhancedCustomShape2d::AppendEnhancedCustomShapeEquationParameter( aEquation, nP3, b3Special );
208 aEquation += ")";
210 break;
211 case 8 :
213 aEquation += "atan2(";
214 EnhancedCustomShape2d::AppendEnhancedCustomShapeEquationParameter( aEquation, nP2, b2Special );
215 aEquation += ",";
216 EnhancedCustomShape2d::AppendEnhancedCustomShapeEquationParameter( aEquation, nP1, b1Special );
217 aEquation += ")/(pi/180)";
219 break;
220 case 9 :
222 EnhancedCustomShape2d::AppendEnhancedCustomShapeEquationParameter( aEquation, nP1, b1Special );
223 aEquation += "*sin(";
224 EnhancedCustomShape2d::AppendEnhancedCustomShapeEquationParameter( aEquation, nP2, b2Special );
225 aEquation += "*(pi/180))";
227 break;
228 case 10 :
230 EnhancedCustomShape2d::AppendEnhancedCustomShapeEquationParameter( aEquation, nP1, b1Special );
231 aEquation += "*cos(";
232 EnhancedCustomShape2d::AppendEnhancedCustomShapeEquationParameter( aEquation, nP2, b2Special );
233 aEquation += "*(pi/180))";
235 break;
236 case 11 :
238 EnhancedCustomShape2d::AppendEnhancedCustomShapeEquationParameter( aEquation, nP1, b1Special );
239 aEquation += "*cos(atan2(";
240 EnhancedCustomShape2d::AppendEnhancedCustomShapeEquationParameter( aEquation, nP3, b3Special );
241 aEquation += ",";
242 EnhancedCustomShape2d::AppendEnhancedCustomShapeEquationParameter( aEquation, nP2, b2Special );
243 aEquation += "))";
245 break;
246 case 12 :
248 EnhancedCustomShape2d::AppendEnhancedCustomShapeEquationParameter( aEquation, nP1, b1Special );
249 aEquation += "*sin(atan2(";
250 EnhancedCustomShape2d::AppendEnhancedCustomShapeEquationParameter( aEquation, nP3, b3Special );
251 aEquation += ",";
252 EnhancedCustomShape2d::AppendEnhancedCustomShapeEquationParameter( aEquation, nP2, b2Special );
253 aEquation += "))";
255 break;
256 case 13 :
258 aEquation += "sqrt(";
259 EnhancedCustomShape2d::AppendEnhancedCustomShapeEquationParameter( aEquation, nP1, b1Special );
260 aEquation += ")";
262 break;
263 case 15 :
265 EnhancedCustomShape2d::AppendEnhancedCustomShapeEquationParameter( aEquation, nP3, b3Special );
266 aEquation += "*sqrt(1-(";
267 EnhancedCustomShape2d::AppendEnhancedCustomShapeEquationParameter( aEquation, nP1, b1Special );
268 aEquation += "/";
269 EnhancedCustomShape2d::AppendEnhancedCustomShapeEquationParameter( aEquation, nP2, b2Special );
270 aEquation += ")"
271 "*(";
272 EnhancedCustomShape2d::AppendEnhancedCustomShapeEquationParameter( aEquation, nP1, b1Special );
273 aEquation += "/";
274 EnhancedCustomShape2d::AppendEnhancedCustomShapeEquationParameter( aEquation, nP2, b2Special );
275 aEquation += "))";
277 break;
278 case 16 :
280 EnhancedCustomShape2d::AppendEnhancedCustomShapeEquationParameter( aEquation, nP1, b1Special );
281 aEquation += "*tan(";
282 EnhancedCustomShape2d::AppendEnhancedCustomShapeEquationParameter( aEquation, nP2, b2Special );
283 aEquation += ")";
285 break;
286 case 0x80 :
288 aEquation += "sqrt(";
289 EnhancedCustomShape2d::AppendEnhancedCustomShapeEquationParameter( aEquation, nP3, b3Special );
290 aEquation += "*";
291 EnhancedCustomShape2d::AppendEnhancedCustomShapeEquationParameter( aEquation, nP3, b3Special );
292 aEquation += "-";
293 EnhancedCustomShape2d::AppendEnhancedCustomShapeEquationParameter( aEquation, nP1, b1Special );
294 aEquation += "*";
295 EnhancedCustomShape2d::AppendEnhancedCustomShapeEquationParameter( aEquation, nP1, b1Special );
296 aEquation += ")";
298 break;
299 case 0x81 :
301 aEquation += "(cos(";
302 EnhancedCustomShape2d::AppendEnhancedCustomShapeEquationParameter( aEquation, nP3, b3Special );
303 aEquation += "*(pi/180))*(";
304 EnhancedCustomShape2d::AppendEnhancedCustomShapeEquationParameter( aEquation, nP1, b1Special );
305 aEquation += "-10800)+sin(";
306 EnhancedCustomShape2d::AppendEnhancedCustomShapeEquationParameter( aEquation, nP3, b3Special );
307 aEquation += "*(pi/180))*(";
308 EnhancedCustomShape2d::AppendEnhancedCustomShapeEquationParameter( aEquation, nP2, b2Special );
309 aEquation += "-10800))+10800";
311 break;
312 case 0x82 :
314 aEquation += "-(sin(";
315 EnhancedCustomShape2d::AppendEnhancedCustomShapeEquationParameter( aEquation, nP3, b3Special );
316 aEquation += "*(pi/180))*(";
317 EnhancedCustomShape2d::AppendEnhancedCustomShapeEquationParameter( aEquation, nP1, b1Special );
318 aEquation += "-10800)-cos(";
319 EnhancedCustomShape2d::AppendEnhancedCustomShapeEquationParameter( aEquation, nP3, b3Special );
320 aEquation += "*(pi/180))*(";
321 EnhancedCustomShape2d::AppendEnhancedCustomShapeEquationParameter( aEquation, nP2, b2Special );
322 aEquation += "-10800))+10800";
324 break;
326 return aEquation;
329 void EnhancedCustomShape2d::AppendEnhancedCustomShapeEquationParameter( OUString& rParameter, const sal_Int32 nPara, const bool bIsSpecialValue )
331 if ( bIsSpecialValue )
333 if ( nPara & 0x400 )
335 rParameter += "?";
336 rParameter += OUString::number( nPara & 0xff );
337 rParameter += " ";
339 else
341 switch( nPara )
343 case DFF_Prop_adjustValue :
344 case DFF_Prop_adjust2Value :
345 case DFF_Prop_adjust3Value :
346 case DFF_Prop_adjust4Value :
347 case DFF_Prop_adjust5Value :
348 case DFF_Prop_adjust6Value :
349 case DFF_Prop_adjust7Value :
350 case DFF_Prop_adjust8Value :
351 case DFF_Prop_adjust9Value :
352 case DFF_Prop_adjust10Value :
354 rParameter += "$";
355 rParameter += OUString::number( nPara - DFF_Prop_adjustValue );
356 rParameter += " ";
358 break;
359 case DFF_Prop_geoLeft :
361 rParameter += "left";
363 break;
364 case DFF_Prop_geoTop :
366 rParameter += "top";
368 break;
369 case DFF_Prop_geoRight :
371 rParameter += "right";
373 break;
374 case DFF_Prop_geoBottom :
376 rParameter += "bottom";
378 break;
382 else
384 rParameter += OUString::number( nPara );
388 void EnhancedCustomShape2d::SetEnhancedCustomShapeHandleParameter( EnhancedCustomShapeParameter& rParameter, const sal_Int32 nPara, const bool bIsSpecialValue, bool bHorz )
390 sal_Int32 nValue = 0;
391 if ( bIsSpecialValue )
393 if ( ( nPara >= 0x100 ) && ( nPara <= 0x107 ) )
395 nValue = nPara & 0xff;
396 rParameter.Type = EnhancedCustomShapeParameterType::ADJUSTMENT;
398 else if ( ( nPara >= 3 ) && ( nPara <= 0x82 ) )
400 nValue = nPara - 3;
401 rParameter.Type = EnhancedCustomShapeParameterType::EQUATION;
403 else if ( nPara == 0 )
405 nValue = 0;
406 if ( bHorz )
407 rParameter.Type = EnhancedCustomShapeParameterType::LEFT;
408 else
409 rParameter.Type = EnhancedCustomShapeParameterType::TOP;
411 else if ( nPara == 1 )
413 nValue = 0;
414 if ( bHorz )
415 rParameter.Type = EnhancedCustomShapeParameterType::RIGHT;
416 else
417 rParameter.Type = EnhancedCustomShapeParameterType::BOTTOM;
419 else if ( nPara == 2 ) // means to be centered, but should not be
420 { // used in our implementation
421 nValue = 5600;
422 rParameter.Type = EnhancedCustomShapeParameterType::NORMAL;
424 else
426 nValue = nPara;
427 rParameter.Type = EnhancedCustomShapeParameterType::NORMAL;
430 else
432 nValue = nPara;
433 rParameter.Type = EnhancedCustomShapeParameterType::NORMAL;
435 rParameter.Value <<= nValue;
438 bool EnhancedCustomShape2d::ConvertSequenceToEnhancedCustomShape2dHandle(
439 const css::beans::PropertyValues& rHandleProperties,
440 EnhancedCustomShape2d::Handle& rDestinationHandle )
442 bool bRetValue = false;
443 if ( rHandleProperties.hasElements() )
445 rDestinationHandle.nFlags = HandleFlags::NONE;
446 for ( const css::beans::PropertyValue& rPropVal : rHandleProperties )
448 if ( rPropVal.Name == "Position" )
450 if ( rPropVal.Value >>= rDestinationHandle.aPosition )
451 bRetValue = true;
453 else if ( rPropVal.Name == "MirroredX" )
455 bool bMirroredX;
456 if ( rPropVal.Value >>= bMirroredX )
458 if ( bMirroredX )
459 rDestinationHandle.nFlags |= HandleFlags::MIRRORED_X;
462 else if ( rPropVal.Name == "MirroredY" )
464 bool bMirroredY;
465 if ( rPropVal.Value >>= bMirroredY )
467 if ( bMirroredY )
468 rDestinationHandle.nFlags |= HandleFlags::MIRRORED_Y;
471 else if ( rPropVal.Name == "Switched" )
473 bool bSwitched;
474 if ( rPropVal.Value >>= bSwitched )
476 if ( bSwitched )
477 rDestinationHandle.nFlags |= HandleFlags::SWITCHED;
480 else if ( rPropVal.Name == "Polar" )
482 if ( rPropVal.Value >>= rDestinationHandle.aPolar )
483 rDestinationHandle.nFlags |= HandleFlags::POLAR;
485 else if ( rPropVal.Name == "RefX" )
487 if ( rPropVal.Value >>= rDestinationHandle.nRefX )
488 rDestinationHandle.nFlags |= HandleFlags::REFX;
490 else if ( rPropVal.Name == "RefY" )
492 if ( rPropVal.Value >>= rDestinationHandle.nRefY )
493 rDestinationHandle.nFlags |= HandleFlags::REFY;
495 else if ( rPropVal.Name == "RefAngle" )
497 if ( rPropVal.Value >>= rDestinationHandle.nRefAngle )
498 rDestinationHandle.nFlags |= HandleFlags::REFANGLE;
500 else if ( rPropVal.Name == "RefR" )
502 if ( rPropVal.Value >>= rDestinationHandle.nRefR )
503 rDestinationHandle.nFlags |= HandleFlags::REFR;
505 else if ( rPropVal.Name == "RadiusRangeMinimum" )
507 if ( rPropVal.Value >>= rDestinationHandle.aRadiusRangeMinimum )
508 rDestinationHandle.nFlags |= HandleFlags::RADIUS_RANGE_MINIMUM;
510 else if ( rPropVal.Name == "RadiusRangeMaximum" )
512 if ( rPropVal.Value >>= rDestinationHandle.aRadiusRangeMaximum )
513 rDestinationHandle.nFlags |= HandleFlags::RADIUS_RANGE_MAXIMUM;
515 else if ( rPropVal.Name == "RangeXMinimum" )
517 if ( rPropVal.Value >>= rDestinationHandle.aXRangeMinimum )
518 rDestinationHandle.nFlags |= HandleFlags::RANGE_X_MINIMUM;
520 else if ( rPropVal.Name == "RangeXMaximum" )
522 if ( rPropVal.Value >>= rDestinationHandle.aXRangeMaximum )
523 rDestinationHandle.nFlags |= HandleFlags::RANGE_X_MAXIMUM;
525 else if ( rPropVal.Name == "RangeYMinimum" )
527 if ( rPropVal.Value >>= rDestinationHandle.aYRangeMinimum )
528 rDestinationHandle.nFlags |= HandleFlags::RANGE_Y_MINIMUM;
530 else if ( rPropVal.Name == "RangeYMaximum" )
532 if ( rPropVal.Value >>= rDestinationHandle.aYRangeMaximum )
533 rDestinationHandle.nFlags |= HandleFlags::RANGE_Y_MAXIMUM;
537 return bRetValue;
540 void EnhancedCustomShape2d::ApplyShapeAttributes( const SdrCustomShapeGeometryItem& rGeometryItem )
542 // AdjustmentValues
543 static constexpr OUStringLiteral sAdjustmentValues( u"AdjustmentValues" );
544 const Any* pAny = const_cast<SdrCustomShapeGeometryItem&>(rGeometryItem).GetPropertyValueByName( sAdjustmentValues );
545 if ( pAny )
546 *pAny >>= seqAdjustmentValues;
549 // Coordsize
550 static constexpr OUStringLiteral sViewBox( u"ViewBox" );
551 const Any* pViewBox = const_cast<SdrCustomShapeGeometryItem&>(rGeometryItem).GetPropertyValueByName( sViewBox );
552 css::awt::Rectangle aViewBox;
553 if ( pViewBox && (*pViewBox >>= aViewBox ) )
555 nCoordLeft = aViewBox.X;
556 nCoordTop = aViewBox.Y;
557 nCoordWidthG = std::abs( aViewBox.Width );
558 nCoordHeightG = std::abs( aViewBox.Height);
560 static constexpr OUStringLiteral sPath( u"Path" );
561 static constexpr OUStringLiteral sCoordinates( u"Coordinates" );
562 static constexpr OUStringLiteral sGluePoints( u"GluePoints" );
563 static constexpr OUStringLiteral sSegments( u"Segments" );
564 static constexpr OUStringLiteral sSubViewSize( u"SubViewSize" );
565 static constexpr OUStringLiteral sStretchX( u"StretchX" );
566 static constexpr OUStringLiteral sStretchY( u"StretchY" );
567 static constexpr OUStringLiteral sTextFrames( u"TextFrames" );
568 static constexpr OUStringLiteral sEquations( u"Equations" );
569 static constexpr OUStringLiteral sHandles( u"Handles" );
572 // Path/Coordinates
573 pAny = const_cast<SdrCustomShapeGeometryItem&>(rGeometryItem).GetPropertyValueByName( sPath, sCoordinates );
574 if ( pAny )
575 *pAny >>= seqCoordinates;
578 // Path/GluePoints
579 pAny = const_cast<SdrCustomShapeGeometryItem&>(rGeometryItem).GetPropertyValueByName( sPath, sGluePoints );
580 if ( pAny )
581 *pAny >>= seqGluePoints;
584 // Path/Segments
585 pAny = const_cast<SdrCustomShapeGeometryItem&>(rGeometryItem).GetPropertyValueByName( sPath, sSegments );
586 if ( pAny )
587 *pAny >>= seqSegments;
590 // Path/SubViewSize
591 pAny = const_cast<SdrCustomShapeGeometryItem&>(rGeometryItem).GetPropertyValueByName( sPath, sSubViewSize );
592 if ( pAny )
593 *pAny >>= seqSubViewSize;
596 // Path/StretchX
597 pAny = const_cast<SdrCustomShapeGeometryItem&>(rGeometryItem).GetPropertyValueByName( sPath, sStretchX );
598 if ( pAny )
600 sal_Int32 nStretchX = 0;
601 if ( *pAny >>= nStretchX )
602 nXRef = nStretchX;
606 // Path/StretchY
607 pAny = const_cast<SdrCustomShapeGeometryItem&>(rGeometryItem).GetPropertyValueByName( sPath, sStretchY );
608 if ( pAny )
610 sal_Int32 nStretchY = 0;
611 if ( *pAny >>= nStretchY )
612 nYRef = nStretchY;
616 // Path/TextFrames
617 pAny = const_cast<SdrCustomShapeGeometryItem&>(rGeometryItem).GetPropertyValueByName( sPath, sTextFrames );
618 if ( pAny )
619 *pAny >>= seqTextFrames;
622 // Equations
623 pAny = const_cast<SdrCustomShapeGeometryItem&>(rGeometryItem).GetPropertyValueByName( sEquations );
624 if ( pAny )
625 *pAny >>= seqEquations;
628 // Handles
629 pAny = const_cast<SdrCustomShapeGeometryItem&>(rGeometryItem).GetPropertyValueByName( sHandles );
630 if ( pAny )
631 *pAny >>= seqHandles;
634 EnhancedCustomShape2d::~EnhancedCustomShape2d()
638 void EnhancedCustomShape2d::SetPathSize( sal_Int32 nIndex )
640 sal_Int32 nWidth = 0;
641 sal_Int32 nHeight = 0;
643 if ( seqSubViewSize.hasElements() && nIndex < seqSubViewSize.getLength() ) {
644 nWidth = seqSubViewSize[ nIndex ].Width;
645 nHeight = seqSubViewSize[ nIndex ].Height;
646 SAL_INFO(
647 "svx",
648 "set subpath " << nIndex << " size: " << nWidth << " x "
649 << nHeight);
652 if ( nWidth && nHeight ) {
653 nCoordWidth = nWidth;
654 nCoordHeight = nHeight;
655 } else {
656 nCoordWidth = nCoordWidthG;
657 nCoordHeight = nCoordHeightG;
660 fXScale = nCoordWidth == 0 ? 0.0 : static_cast<double>(aLogicRect.GetWidth()) / static_cast<double>(nCoordWidth);
661 fYScale = nCoordHeight == 0 ? 0.0 : static_cast<double>(aLogicRect.GetHeight()) / static_cast<double>(nCoordHeight);
662 if ( bOOXMLShape )
664 SAL_INFO(
665 "svx",
666 "ooxml shape, path width: " << nCoordWidth << " height: "
667 << nCoordHeight);
669 // Try to set up scale separately, if given only width or height
670 // This is possible case in OOXML when only width or height is non-zero
671 if ( nCoordWidth == 0 )
673 if ( nWidth )
674 fXScale = static_cast<double>(aLogicRect.GetWidth()) / static_cast<double>(nWidth);
675 else
676 fXScale = 1.0;
678 if ( nCoordHeight == 0 )
680 if ( nHeight )
681 fYScale = static_cast<double>(aLogicRect.GetHeight()) / static_cast<double>(nHeight);
682 else
683 fYScale = 1.0;
686 if ( static_cast<sal_uInt32>(nXRef) != 0x80000000 && aLogicRect.GetHeight() )
688 fXRatio = static_cast<double>(aLogicRect.GetWidth()) / static_cast<double>(aLogicRect.GetHeight());
689 if ( fXRatio > 1 )
690 fXScale /= fXRatio;
691 else
692 fXRatio = 1.0;
694 else
695 fXRatio = 1.0;
696 if ( static_cast<sal_uInt32>(nYRef) != 0x80000000 && aLogicRect.GetWidth() )
698 fYRatio = static_cast<double>(aLogicRect.GetHeight()) / static_cast<double>(aLogicRect.GetWidth());
699 if ( fYRatio > 1 )
700 fYScale /= fYRatio;
701 else
702 fYRatio = 1.0;
704 else
705 fYRatio = 1.0;
708 EnhancedCustomShape2d::EnhancedCustomShape2d(SdrObjCustomShape& rSdrObjCustomShape)
709 : SfxItemSet ( rSdrObjCustomShape.GetMergedItemSet() ),
710 mrSdrObjCustomShape ( rSdrObjCustomShape ),
711 eSpType ( mso_sptNil ),
712 nCoordLeft ( 0 ),
713 nCoordTop ( 0 ),
714 nCoordWidthG ( 21600 ),
715 nCoordHeightG ( 21600 ),
716 bOOXMLShape ( false ),
717 nXRef ( 0x80000000 ),
718 nYRef ( 0x80000000 ),
719 nColorData ( 0 ),
720 bFilled ( rSdrObjCustomShape.GetMergedItem( XATTR_FILLSTYLE ).GetValue() != drawing::FillStyle_NONE ),
721 bStroked ( rSdrObjCustomShape.GetMergedItem( XATTR_LINESTYLE ).GetValue() != drawing::LineStyle_NONE ),
722 bFlipH ( false ),
723 bFlipV ( false )
725 // bTextFlow needs to be set before clearing the TextDirection Item
727 ClearItem( SDRATTR_TEXTDIRECTION ); //SJ: vertical writing is not required, by removing this item no outliner is created
729 // #i105323# For 2D AutoShapes, the shadow attribute does not need to be applied to any
730 // of the constructed helper SdrObjects. This would lead to problems since the shadow
731 // of one helper object would fall on one helper object behind it (e.g. with the
732 // eyes of the smiley shape). This is not wanted; instead a single shadow 'behind'
733 // the AutoShape visualisation is wanted. This is done with primitive functionality
734 // now in SdrCustomShapePrimitive2D::create2DDecomposition, but only for 2D objects
735 // (see there and in EnhancedCustomShape3d::Create3DObject to read more).
736 // This exception may be removed later when AutoShapes will create primitives directly.
737 // So, currently remove the ShadowAttribute from the ItemSet to not apply it to any
738 // 2D helper shape.
739 ClearItem(SDRATTR_SHADOW);
741 Point aP( mrSdrObjCustomShape.GetSnapRect().Center() );
742 Size aS( mrSdrObjCustomShape.GetLogicRect().GetSize() );
743 aP.AdjustX( -(aS.Width() / 2) );
744 aP.AdjustY( -(aS.Height() / 2) );
745 aLogicRect = tools::Rectangle( aP, aS );
747 OUString sShapeType;
748 const SdrCustomShapeGeometryItem& rGeometryItem(mrSdrObjCustomShape.GetMergedItem( SDRATTR_CUSTOMSHAPE_GEOMETRY ));
749 static constexpr OUStringLiteral sType = u"Type";
750 const Any* pAny = rGeometryItem.GetPropertyValueByName( sType );
751 if ( pAny ) {
752 *pAny >>= sShapeType;
753 bOOXMLShape = sShapeType.startsWith("ooxml-");
754 SAL_INFO("svx", "shape type: " << sShapeType << " " << bOOXMLShape);
756 eSpType = EnhancedCustomShapeTypeNames::Get( sShapeType );
758 static constexpr OUStringLiteral sMirroredX = u"MirroredX";
759 static constexpr OUStringLiteral sMirroredY = u"MirroredY";
760 pAny = rGeometryItem.GetPropertyValueByName( sMirroredX );
761 if ( pAny )
762 *pAny >>= bFlipH;
763 pAny = rGeometryItem.GetPropertyValueByName( sMirroredY );
764 if ( pAny )
765 *pAny >>= bFlipV;
767 nRotateAngle = Degree100(static_cast<sal_Int32>(mrSdrObjCustomShape.GetObjectRotation() * 100.0));
769 /*const sal_Int32* pDefData =*/ ApplyShapeAttributes( rGeometryItem );
770 SetPathSize();
772 switch( eSpType )
774 case mso_sptCan : nColorData = 0x20400000; break;
775 case mso_sptCube : nColorData = 0x302e0000; break;
776 case mso_sptActionButtonBlank : nColorData = 0x502ce400; break;
777 case mso_sptActionButtonHome : nColorData = 0x702ce4ce; break;
778 case mso_sptActionButtonHelp : nColorData = 0x602ce4c0; break;
779 case mso_sptActionButtonInformation : nColorData = 0x702ce4c5; break;
780 case mso_sptActionButtonBackPrevious : nColorData = 0x602ce4c0; break;
781 case mso_sptActionButtonForwardNext : nColorData = 0x602ce4c0; break;
782 case mso_sptActionButtonBeginning : nColorData = 0x602ce4c0; break;
783 case mso_sptActionButtonEnd : nColorData = 0x602ce4c0; break;
784 case mso_sptActionButtonReturn : nColorData = 0x602ce4c0; break;
785 case mso_sptActionButtonDocument : nColorData = 0x702ce4ec; break;
786 case mso_sptActionButtonSound : nColorData = 0x602ce4c0; break;
787 case mso_sptActionButtonMovie : nColorData = 0x602ce4c0; break;
788 case mso_sptBevel : nColorData = 0x502ce400; break;
789 case mso_sptFoldedCorner : nColorData = 0x20e00000; break;
790 case mso_sptSmileyFace : nColorData = 0x20e00000; break;
791 case mso_sptNil :
793 // Because calculation method has changed in #i102797 original color encoding for
794 // Octagon Bevel and Diamond Bevel can no longer be used. We keep the color coding
795 // only for self-created shapes, as authors may have already considered the change.
796 // We use ColorData compatible to OOXML.
797 if (sShapeType == "col-60da8460") // Octagon Bevel
799 nColorData = 0x60ecc240;
801 else if (sShapeType == "col-502ad400") // Diamond Bevel
803 nColorData = 0x502ce400;
805 else if (sShapeType.getLength() > 4 && sShapeType.match( "col-" ))
807 nColorData = o3tl::toUInt32(sShapeType.subView( 4 ), 16);
810 break;
811 case mso_sptCurvedLeftArrow :
812 case mso_sptCurvedRightArrow :
813 case mso_sptCurvedUpArrow :
814 case mso_sptCurvedDownArrow : nColorData = 0x20d00000; break;
815 case mso_sptRibbon2 : nColorData = 0x30ee0000; break;
816 case mso_sptRibbon : nColorData = 0x30ee0000; break;
818 case mso_sptEllipseRibbon2 : nColorData = 0x30ee0000; break;
819 case mso_sptEllipseRibbon : nColorData = 0x30ee0000; break;
821 case mso_sptVerticalScroll : nColorData = 0x30ee0000; break;
822 case mso_sptHorizontalScroll : nColorData = 0x30ee0000; break;
823 default:
824 break;
827 sal_Int32 nLength = seqEquations.getLength();
829 if ( !nLength )
830 return;
832 vNodesSharedPtr.resize( nLength );
833 vEquationResults.resize( nLength );
834 for ( sal_Int32 i = 0; i < nLength; i++ )
836 vEquationResults[ i ].bReady = false;
839 vNodesSharedPtr[ i ] = EnhancedCustomShape::FunctionParser::parseFunction( seqEquations[ i ], *this );
841 catch ( EnhancedCustomShape::ParseError& )
843 SAL_INFO(
844 "svx",
845 "error: equation number: " << i << ", parser failed ("
846 << seqEquations[i] << ")");
851 using EnhancedCustomShape::ExpressionFunct;
853 double EnhancedCustomShape2d::GetEnumFunc( const ExpressionFunct eFunc ) const
855 double fRet = 0.0;
856 switch( eFunc )
858 case ExpressionFunct::EnumPi : fRet = M_PI; break;
859 case ExpressionFunct::EnumLeft : fRet = static_cast<double>(nCoordLeft); break;
860 case ExpressionFunct::EnumTop : fRet = static_cast<double>(nCoordTop); break;
861 case ExpressionFunct::EnumRight : fRet = (static_cast<double>(nCoordLeft) + static_cast<double>(nCoordWidth)) * fXRatio; break;
862 case ExpressionFunct::EnumBottom : fRet = (static_cast<double>(nCoordTop) + static_cast<double>(nCoordHeight)) * fYRatio; break;
863 case ExpressionFunct::EnumXStretch : fRet = nXRef; break;
864 case ExpressionFunct::EnumYStretch : fRet = nYRef; break;
865 case ExpressionFunct::EnumHasStroke : fRet = bStroked ? 1.0 : 0.0; break;
866 case ExpressionFunct::EnumHasFill : fRet = bFilled ? 1.0 : 0.0; break;
867 case ExpressionFunct::EnumWidth : fRet = nCoordWidth; break;
868 case ExpressionFunct::EnumHeight : fRet = nCoordHeight; break;
869 case ExpressionFunct::EnumLogWidth : fRet = aLogicRect.GetWidth(); break;
870 case ExpressionFunct::EnumLogHeight : fRet = aLogicRect.GetHeight(); break;
871 default: break;
873 return fRet;
875 double EnhancedCustomShape2d::GetAdjustValueAsDouble( const sal_Int32 nIndex ) const
877 double fNumber = 0.0;
878 if ( nIndex < seqAdjustmentValues.getLength() )
880 if ( seqAdjustmentValues[ nIndex ].Value.getValueTypeClass() == TypeClass_DOUBLE )
881 seqAdjustmentValues[ nIndex ].Value >>= fNumber;
882 else
884 sal_Int32 nNumber = 0;
885 seqAdjustmentValues[ nIndex ].Value >>= nNumber;
886 fNumber = static_cast<double>(nNumber);
889 return fNumber;
891 double EnhancedCustomShape2d::GetEquationValueAsDouble( const sal_Int32 nIndex ) const
893 double fNumber = 0.0;
894 static sal_uInt32 nLevel = 0;
895 if ( nIndex < static_cast<sal_Int32>(vNodesSharedPtr.size()) )
897 if ( vNodesSharedPtr[ nIndex ] ) {
898 nLevel ++;
901 if ( vEquationResults[ nIndex ].bReady )
902 fNumber = vEquationResults[ nIndex ].fValue;
903 else {
904 // cast to non const, so that we can optimize by caching
905 // equation results, without changing all the const in the stack
906 struct EquationResult &aResult = const_cast<EnhancedCustomShape2d*>(this)->vEquationResults[ nIndex ];
908 fNumber = aResult.fValue = (*vNodesSharedPtr[ nIndex ])();
909 aResult.bReady = true;
911 SAL_INFO("svx", "equation " << nLevel << " (level: " << seqEquations[nIndex] << "): "
912 << fNumber << " --> " << 180.0*fNumber/10800000.0);
914 if ( !std::isfinite( fNumber ) )
915 fNumber = 0.0;
917 catch ( ... )
919 SAL_WARN("svx", "EnhancedCustomShape2d::GetEquationValueAsDouble failed");
921 nLevel --;
923 SAL_INFO(
924 "svx",
925 "?" << nIndex << " --> " << fNumber << " (angle: "
926 << 180.0*fNumber/10800000.0 << ")");
929 return fNumber;
932 bool EnhancedCustomShape2d::SetAdjustValueAsDouble( const double& rValue, const sal_Int32 nIndex )
934 bool bRetValue = false;
935 if ( nIndex < seqAdjustmentValues.getLength() )
937 // updating our local adjustment sequence
938 auto pseqAdjustmentValues = seqAdjustmentValues.getArray();
939 pseqAdjustmentValues[ nIndex ].Value <<= rValue;
940 pseqAdjustmentValues[ nIndex ].State = css::beans::PropertyState_DIRECT_VALUE;
941 bRetValue = true;
943 return bRetValue;
946 basegfx::B2DPoint EnhancedCustomShape2d::GetPointAsB2DPoint( const css::drawing::EnhancedCustomShapeParameterPair& rPair,
947 const bool bScale, const bool bReplaceGeoSize ) const
949 double fValX, fValY;
950 // width
951 GetParameter(fValX, rPair.First, bReplaceGeoSize, false);
952 fValX -= nCoordLeft;
953 if (bScale)
955 fValX *= fXScale;
957 // height
958 GetParameter(fValY, rPair.Second, false, bReplaceGeoSize);
959 fValY -= nCoordTop;
960 if (bScale)
962 fValY *= fYScale;
964 return basegfx::B2DPoint(fValX,fValY);
967 Point EnhancedCustomShape2d::GetPoint( const css::drawing::EnhancedCustomShapeParameterPair& rPair,
968 const bool bScale, const bool bReplaceGeoSize ) const
970 basegfx::B2DPoint aPoint(GetPointAsB2DPoint(rPair, bScale, bReplaceGeoSize));
971 return Point(static_cast<tools::Long>(aPoint.getX()), static_cast<tools::Long>(aPoint.getY()));
974 void EnhancedCustomShape2d::GetParameter( double& rRetValue, const EnhancedCustomShapeParameter& rParameter,
975 const bool bReplaceGeoWidth, const bool bReplaceGeoHeight ) const
977 rRetValue = 0.0;
978 switch ( rParameter.Type )
980 case EnhancedCustomShapeParameterType::ADJUSTMENT :
982 sal_Int32 nAdjustmentIndex = 0;
983 if ( rParameter.Value >>= nAdjustmentIndex )
985 rRetValue = GetAdjustValueAsDouble( nAdjustmentIndex );
988 break;
989 case EnhancedCustomShapeParameterType::EQUATION :
991 sal_Int32 nEquationIndex = 0;
992 if ( rParameter.Value >>= nEquationIndex )
994 rRetValue = GetEquationValueAsDouble( nEquationIndex );
997 break;
998 case EnhancedCustomShapeParameterType::NORMAL :
1000 if ( rParameter.Value.getValueTypeClass() == TypeClass_DOUBLE )
1002 double fValue(0.0);
1003 if ( rParameter.Value >>= fValue )
1005 rRetValue = fValue;
1008 else
1010 sal_Int32 nValue = 0;
1011 if ( rParameter.Value >>= nValue )
1013 rRetValue = nValue;
1014 if ( bReplaceGeoWidth && ( nValue == nCoordWidth ) )
1015 rRetValue *= fXRatio;
1016 else if ( bReplaceGeoHeight && ( nValue == nCoordHeight ) )
1017 rRetValue *= fYRatio;
1021 break;
1022 case EnhancedCustomShapeParameterType::LEFT :
1024 rRetValue = 0.0;
1026 break;
1027 case EnhancedCustomShapeParameterType::TOP :
1029 rRetValue = 0.0;
1031 break;
1032 case EnhancedCustomShapeParameterType::RIGHT :
1034 rRetValue = nCoordWidth;
1036 break;
1037 case EnhancedCustomShapeParameterType::BOTTOM :
1039 rRetValue = nCoordHeight;
1041 break;
1045 // nLumDat 28-31 = number of luminance entries in nLumDat
1046 // nLumDat 27-24 = nLumDatEntry 0
1047 // nLumDat 23-20 = nLumDatEntry 1 ...
1048 // each 4bit entry is to be interpreted as a 10 percent signed luminance changing
1049 sal_Int32 EnhancedCustomShape2d::GetLuminanceChange( sal_uInt32 nIndex ) const
1051 const sal_uInt32 nCount = nColorData >> 28;
1052 if ( !nCount )
1053 return 0;
1055 if ( nIndex >= nCount )
1056 nIndex = nCount - 1;
1058 const sal_Int32 nLumDat = nColorData << ( ( 1 + nIndex ) << 2 );
1059 return ( nLumDat >> 28 ) * 10;
1062 Color EnhancedCustomShape2d::GetColorData( const Color& rFillColor, sal_uInt32 nIndex, double dBrightness ) const
1064 if ( bOOXMLShape || ( mso_sptMin == eSpType /* ODF "non-primitive" */ ) )
1065 { //do LibreOffice way, using dBrightness
1066 if ( dBrightness == 0.0)
1068 return rFillColor;
1070 else
1072 if (dBrightness >=0.0)
1073 { //lighten, blending with white
1074 return Color( static_cast<sal_uInt8>(static_cast< sal_Int32 >( std::clamp(rFillColor.GetRed() * (1.0-dBrightness) + dBrightness * 255.0, 0.0, 255.0) )),
1075 static_cast<sal_uInt8>(static_cast< sal_Int32 >( std::clamp(rFillColor.GetGreen() * (1.0-dBrightness) + dBrightness * 255.0, 0.0, 255.0) )),
1076 static_cast<sal_uInt8>(static_cast< sal_Int32 >( std::clamp(rFillColor.GetBlue() * (1.0-dBrightness) + dBrightness * 255.0, 0.0, 255.0) )) );
1078 else
1079 { //darken (indicated by negative sign), blending with black
1080 return Color( static_cast<sal_uInt8>(static_cast< sal_Int32 >( std::clamp(rFillColor.GetRed() * (1.0+dBrightness), 0.0, 255.0) )),
1081 static_cast<sal_uInt8>(static_cast< sal_Int32 >( std::clamp(rFillColor.GetGreen() * (1.0+dBrightness), 0.0, 255.0) )),
1082 static_cast<sal_uInt8>(static_cast< sal_Int32 >( std::clamp(rFillColor.GetBlue() * (1.0+dBrightness), 0.0, 255.0) )) );
1086 else
1087 { //do OpenOffice way, using nColorData
1088 const sal_Int32 nLuminance = GetLuminanceChange(nIndex);
1089 if( !nLuminance )
1090 return rFillColor;
1092 basegfx::BColor aHSVColor=
1093 basegfx::utils::rgb2hsv(
1094 basegfx::BColor(rFillColor.GetRed()/255.0,
1095 rFillColor.GetGreen()/255.0,
1096 rFillColor.GetBlue()/255.0));
1098 if( nLuminance > 0 )
1100 aHSVColor.setGreen(
1101 aHSVColor.getGreen() * (1.0-nLuminance/100.0));
1102 aHSVColor.setBlue(
1103 nLuminance/100.0 +
1104 (1.0-nLuminance/100.0)*aHSVColor.getBlue());
1106 else if( nLuminance < 0 )
1108 aHSVColor.setBlue(
1109 (1.0+nLuminance/100.0)*aHSVColor.getBlue());
1112 aHSVColor = basegfx::utils::hsv2rgb(aHSVColor);
1113 return Color( static_cast<sal_uInt8>(static_cast< sal_Int32 >( std::clamp(aHSVColor.getRed(),0.0,1.0) * 255.0 + 0.5 )),
1114 static_cast<sal_uInt8>(static_cast< sal_Int32 >( std::clamp(aHSVColor.getGreen(),0.0,1.0) * 255.0 + 0.5 )),
1115 static_cast<sal_uInt8>(static_cast< sal_Int32 >( std::clamp(aHSVColor.getBlue(),0.0,1.0) * 255.0 + 0.5 )) );
1119 tools::Rectangle EnhancedCustomShape2d::GetTextRect() const
1121 if ( !seqTextFrames.hasElements() )
1122 return aLogicRect;
1123 sal_Int32 nIndex = 0;
1124 Point aTopLeft( GetPoint( seqTextFrames[ nIndex ].TopLeft, !bOOXMLShape, true ) );
1125 Point aBottomRight( GetPoint( seqTextFrames[ nIndex ].BottomRight, !bOOXMLShape, true ) );
1126 tools::Rectangle aRect( aTopLeft, aBottomRight );
1127 if ( bFlipH )
1129 aRect.SetLeft(aLogicRect.GetWidth() - 1 - aBottomRight.X());
1130 aRect.SetRight( aLogicRect.GetWidth() - 1 - aTopLeft.X());
1132 if ( bFlipV )
1134 aRect.SetTop(aLogicRect.GetHeight() - 1 - aBottomRight.Y());
1135 aRect.SetBottom(aLogicRect.GetHeight() - 1 - aTopLeft.Y());
1137 SAL_INFO("svx", aRect.GetWidth() << " x " << aRect.GetHeight());
1138 if( aRect.GetWidth() <= 1 || aRect.GetHeight() <= 1 )
1139 return aLogicRect;
1140 aRect.Move( aLogicRect.Left(), aLogicRect.Top() );
1141 aRect.Normalize();
1142 return aRect;
1145 sal_uInt32 EnhancedCustomShape2d::GetHdlCount() const
1147 return seqHandles.getLength();
1150 bool EnhancedCustomShape2d::GetHandlePosition( const sal_uInt32 nIndex, Point& rReturnPosition ) const
1152 bool bRetValue = false;
1153 if ( nIndex < GetHdlCount() )
1155 Handle aHandle;
1156 if ( ConvertSequenceToEnhancedCustomShape2dHandle( seqHandles[ nIndex ], aHandle ) )
1158 if ( aHandle.nFlags & HandleFlags::POLAR )
1160 Point aReferencePoint( GetPoint( aHandle.aPolar ) );
1162 double fAngle;
1163 double fRadius;
1164 GetParameter( fRadius, aHandle.aPosition.First, false, false );
1165 GetParameter( fAngle, aHandle.aPosition.Second, false, false );
1167 double a = basegfx::deg2rad(360.0 - fAngle);
1168 double dx = fRadius * fXScale;
1169 double fX = dx * cos( a );
1170 double fY =-dx * sin( a );
1171 rReturnPosition =
1172 Point(
1173 FRound( fX + aReferencePoint.X() ),
1174 basegfx::fTools::equalZero(fXScale) ? aReferencePoint.Y() :
1175 FRound( ( fY * fYScale ) / fXScale + aReferencePoint.Y() ) );
1177 else
1179 if ( aHandle.nFlags & HandleFlags::SWITCHED )
1181 if ( aLogicRect.GetHeight() > aLogicRect.GetWidth() )
1183 css::drawing::EnhancedCustomShapeParameter aFirst = aHandle.aPosition.First;
1184 css::drawing::EnhancedCustomShapeParameter aSecond = aHandle.aPosition.Second;
1185 aHandle.aPosition.First = aSecond;
1186 aHandle.aPosition.Second = aFirst;
1189 if (bOOXMLShape)
1190 rReturnPosition = GetPoint(aHandle.aPosition, false /*bScale*/);
1191 else
1192 rReturnPosition = GetPoint(aHandle.aPosition, true /*bScale*/);
1194 const GeoStat aGeoStat(mrSdrObjCustomShape.GetGeoStat());
1195 if ( aGeoStat.nShearAngle )
1197 double nTan = aGeoStat.mfTanShearAngle;
1198 if (bFlipV != bFlipH)
1199 nTan = -nTan;
1200 ShearPoint( rReturnPosition, Point( aLogicRect.GetWidth() / 2, aLogicRect.GetHeight() / 2 ), nTan );
1202 if ( nRotateAngle )
1204 double a = toRadians(nRotateAngle);
1205 RotatePoint( rReturnPosition, Point( aLogicRect.GetWidth() / 2, aLogicRect.GetHeight() / 2 ), sin( a ), cos( a ) );
1207 if ( bFlipH )
1208 rReturnPosition.setX( aLogicRect.GetWidth() - rReturnPosition.X() );
1209 if ( bFlipV )
1210 rReturnPosition.setY( aLogicRect.GetHeight() - rReturnPosition.Y() );
1211 rReturnPosition.Move( aLogicRect.Left(), aLogicRect.Top() );
1212 bRetValue = true;
1215 return bRetValue;
1218 static double lcl_getXAdjustmentValue(std::u16string_view rShapeType, const sal_uInt32 nHandleIndex,
1219 const double fX, const double fW, const double fH)
1221 // degenerated shapes are not worth to calculate special case for each shape type
1222 if (fW <= 0.0 || fH <= 0.0)
1223 return 50000;
1225 // pattern (w - x) / ss * 100000 or (r - x) / ss * 100000
1226 if ((rShapeType == u"ooxml-bentArrow" && nHandleIndex == 2) || (rShapeType == u"ooxml-chevron")
1227 || (rShapeType == u"ooxml-curvedRightArrow") || (rShapeType == u"ooxml-foldedCorner")
1228 || (rShapeType == u"ooxml-homePlate") || (rShapeType == u"ooxml-notchedRightArrow")
1229 || (rShapeType == u"ooxml-nonIsoscelesTrapezoid" && nHandleIndex == 1)
1230 || (rShapeType == u"ooxml-rightArrow")
1231 || (rShapeType == u"ooxml-rightArrowCallout" && nHandleIndex == 2)
1232 || (rShapeType == u"ooxml-round1Rect")
1233 || (rShapeType == u"ooxml-round2DiagRect" && nHandleIndex == 1)
1234 || (rShapeType == u"ooxml-round2SameRect" && nHandleIndex == 0)
1235 || (rShapeType == u"ooxml-snip1Rect")
1236 || (rShapeType == u"ooxml-snip2DiagRect" && nHandleIndex == 1)
1237 || (rShapeType == u"ooxml-snip2SameRect" && nHandleIndex == 0)
1238 || (rShapeType == u"ooxml-snipRoundRect" && nHandleIndex == 1)
1239 || (rShapeType == u"ooxml-swooshArrow") || (rShapeType == u"ooxml-stripedRightArrow"))
1240 return (fW - fX) / std::min(fW, fH) * 100000.0;
1242 // pattern x / ss * 100000 or (x - l) / ss * 100000
1243 if ((rShapeType == u"ooxml-bentArrow" && nHandleIndex == 0)
1244 || (rShapeType == u"ooxml-bentArrow" && nHandleIndex == 3)
1245 || (rShapeType == u"ooxml-corner")
1246 || (rShapeType == u"ooxml-curvedDownArrow") || (rShapeType == u"ooxml-curvedLeftArrow")
1247 || (rShapeType == u"ooxml-curvedUpArrow") || (rShapeType == u"ooxml-leftArrow")
1248 || (rShapeType == u"ooxml-leftArrowCallout" && nHandleIndex == 2)
1249 || (rShapeType == u"ooxml-leftRightArrow")
1250 || (rShapeType == u"ooxml-leftRightArrowCallout" && nHandleIndex == 2)
1251 || (rShapeType == u"ooxml-leftRightRibbon")
1252 || (rShapeType == u"ooxml-nonIsoscelesTrapezoid" && nHandleIndex == 0)
1253 || (rShapeType == u"ooxml-parallelogram")
1254 || (rShapeType == u"ooxml-round2DiagRect" && nHandleIndex == 0)
1255 || (rShapeType == u"ooxml-round2SameRect" && nHandleIndex == 1)
1256 || (rShapeType == u"ooxml-roundRect")
1257 || (rShapeType == u"ooxml-snip2DiagRect" && nHandleIndex == 0)
1258 || (rShapeType == u"ooxml-snip2SameRect" && nHandleIndex == 1)
1259 || (rShapeType == u"ooxml-snipRoundRect" && nHandleIndex == 0)
1260 || (rShapeType == u"ooxml-uturnArrow" && nHandleIndex == 0)
1261 || (rShapeType == u"ooxml-uturnArrow" && nHandleIndex == 3))
1262 return fX / std::min(fW, fH) * 100000.0;
1264 // pattern (hc - x) / ss * 200000
1265 if ((rShapeType == u"ooxml-downArrowCallout" && nHandleIndex == 0)
1266 || (rShapeType == u"ooxml-leftRightUpArrow" && nHandleIndex == 0)
1267 || (rShapeType == u"ooxml-quadArrow" && nHandleIndex == 0)
1268 || (rShapeType == u"ooxml-quadArrowCallout" && nHandleIndex == 0)
1269 || (rShapeType == u"ooxml-upArrowCallout" && nHandleIndex == 0)
1270 || (rShapeType == u"ooxml-upDownArrowCallout" && nHandleIndex == 0))
1271 return (fW / 2.0 - fX) / std::min(fW, fH) * 200000.0;
1273 // pattern (hc - x) / ss * 100000
1274 if ((rShapeType == u"ooxml-downArrowCallout" && nHandleIndex == 1)
1275 || (rShapeType == u"ooxml-leftRightUpArrow" && nHandleIndex == 1)
1276 || (rShapeType == u"ooxml-quadArrow" && nHandleIndex == 1)
1277 || (rShapeType == u"ooxml-quadArrowCallout" && nHandleIndex == 1)
1278 || (rShapeType == u"ooxml-upArrowCallout" && nHandleIndex == 1)
1279 || (rShapeType == u"ooxml-upDownArrowCallout" && nHandleIndex == 1))
1280 return (fW / 2.0 - fX) / std::min(fW, fH) * 100000.0;
1282 // pattern (w - x) / ss * 50000 or (r - x) / ss * 50000
1283 if ((rShapeType == u"ooxml-bentUpArrow") || (rShapeType == u"ooxml-leftUpArrow")
1284 || (rShapeType == u"ooxml-uturnArrow" && nHandleIndex == 1))
1285 return (fW - fX) / std::min(fW, fH) * 50000.0;
1287 // pattern x / ss * 200000
1288 if (rShapeType == u"ooxml-nonIsoscelesTrapezoid" && nHandleIndex == 0)
1289 return fX / std::min(fW, fH) * 200000.0;
1291 // pattern (hc - x) / w * 200000
1292 if ((rShapeType == u"ooxml-downArrow" && nHandleIndex == 0)
1293 || (rShapeType == u"ooxml-ellipseRibbon") || (rShapeType == u"ooxml-ellipseRibbon2")
1294 || (rShapeType == u"ooxml-leftRightArrowCallout" && nHandleIndex == 3)
1295 || (rShapeType == u"ooxml-ribbon") || (rShapeType == u"ooxml-ribbon2")
1296 || (rShapeType == u"ooxml-upArrow" && nHandleIndex == 0)
1297 || (rShapeType == u"ooxml-upDownArrow" && nHandleIndex == 0))
1298 return (fW / 2.0 - fX) / fW * 200000.0;
1300 // pattern (x - hc) / w * 100000
1301 if ((rShapeType == u"ooxml-cloudCallout") || (rShapeType == u"ooxml-doubleWave")
1302 || (rShapeType == u"ooxml-wave") || (rShapeType == u"ooxml-wedgeEllipseCallout")
1303 || (rShapeType == u"ooxml-wedgeRectCallout")
1304 || (rShapeType == u"ooxml-wedgeRoundRectCallout"))
1305 return (fX - fW / 2.0) / fW * 100000.0;
1307 // pattern (x - hc) / w * 200000
1308 if (rShapeType == u"ooxml-teardrop")
1309 return (fX - fW / 2.0) / fW * 200000.0;
1311 // pattern (w - x) / w * 100000 or (r - x) / w * 100000
1312 if (rShapeType == u"ooxml-leftArrowCallout" && nHandleIndex == 3)
1313 return (fW - fX) / fW * 100000.0;
1315 // pattern (hc - x) / h * 100000
1316 if (rShapeType == u"ooxml-mathDivide")
1317 return (fW / 2.0 - fX) / fH * 100000.0;
1319 // pattern x / w * 100000, simple scaling
1320 if (o3tl::starts_with(rShapeType, u"ooxml-"))
1321 return fX / fW * 100000.0;
1323 return fX; // method is unknown
1326 static double lcl_getYAdjustmentValue(std::u16string_view rShapeType, const sal_uInt32 nHandleIndex,
1327 const double fY, const double fW, const double fH)
1329 // degenerated shapes are not worth to calculate a special case for each shape type
1330 if (fW <= 0.0 || fH <= 0.0)
1331 return 50000;
1333 // pattern (vc - y) / ss * 100000
1334 if ((rShapeType == u"ooxml-leftArrowCallout" && nHandleIndex == 1)
1335 || (rShapeType == u"ooxml-leftRightArrowCallout" && nHandleIndex == 1)
1336 || (rShapeType == u"ooxml-rightArrowCallout" && nHandleIndex == 1))
1337 return (fH / 2.0 - fY) / std::min(fW, fH) * 100000.0;
1339 // pattern (vc - y) / ss * 200000
1340 if ((rShapeType == u"ooxml-curvedLeftArrow") || (rShapeType == u"ooxml-curvedRightArrow")
1341 || (rShapeType == u"ooxml-leftArrowCallout" && nHandleIndex == 0)
1342 || (rShapeType == u"ooxml-leftRightArrowCallout" && nHandleIndex == 0)
1343 || (rShapeType == u"ooxml-mathPlus")
1344 || (rShapeType == u"ooxml-rightArrowCallout" && nHandleIndex == 0))
1345 return (fH / 2.0 - fY) / std::min(fW, fH) * 200000.0;
1347 // pattern (h - y) / ss * 100000 or (b - y) / ss * 100000
1348 if ((rShapeType == u"ooxml-bentUpArrow" && nHandleIndex == 0) || (rShapeType == u"ooxml-corner")
1349 || (rShapeType == u"ooxml-curvedDownArrow") || (rShapeType == u"ooxml-downArrow")
1350 || (rShapeType == u"ooxml-downArrowCallout" && nHandleIndex == 2)
1351 || (rShapeType == u"ooxml-uturnArrow" && nHandleIndex == 2))
1352 return (fH - fY) / std::min(fW, fH) * 100000.0;
1354 // pattern (h - y) / ss * 200000 or (b - y) / ss * 200000
1355 if (rShapeType == u"ooxml-leftUpArrow" && nHandleIndex == 0) // - adj2 * 2 outside
1356 return (fH - fY) / std::min(fW, fH) * 200000.0;
1358 // pattern y / ss * 100000 or (y - t) / ss * 100000
1359 if ((rShapeType == u"ooxml-bentUpArrow" && nHandleIndex == 2)
1360 || (rShapeType == u"ooxml-bracePair") || (rShapeType == u"ooxml-bracketPair")
1361 || (rShapeType == u"ooxml-can") || (rShapeType == u"ooxml-cube")
1362 || (rShapeType == u"ooxml-curvedUpArrow") || (rShapeType == u"ooxml-halfFrame")
1363 || (rShapeType == u"ooxml-leftBrace" && nHandleIndex == 0)
1364 || (rShapeType == u"ooxml-leftBracket") || (rShapeType == u"ooxml-leftRightUpArrow")
1365 || (rShapeType == u"ooxml-leftUpArrow" && nHandleIndex == 2)
1366 || (rShapeType == u"ooxml-mathMultiply") || (rShapeType == u"ooxml-quadArrow")
1367 || (rShapeType == u"ooxml-quadArrowCallout" && nHandleIndex == 2)
1368 || (rShapeType == u"ooxml-rightBrace" && nHandleIndex == 0)
1369 || (rShapeType == u"ooxml-rightBracket") || (rShapeType == u"ooxml-upArrow")
1370 || (rShapeType == u"ooxml-upArrowCallout" && nHandleIndex == 2)
1371 || (rShapeType == u"ooxml-upDownArrow")
1372 || (rShapeType == u"ooxml-upDownArrowCallout" && nHandleIndex == 2)
1373 || (rShapeType == u"ooxml-verticalScroll"))
1374 return fY / std::min(fW, fH) * 100000.0;
1376 // pattern y / ss * 50000
1377 if (rShapeType == u"ooxml-bentArrow")
1378 return fY / std::min(fW, fH) * 50000.0;
1380 // pattern (vc - y) / h * 100000
1381 if ((rShapeType == u"ooxml-mathDivide" && nHandleIndex == 1) // -adj1 / 2 - adj3 outside
1382 || (rShapeType == u"ooxml-mathEqual" && nHandleIndex == 0) // -adj2 / 2 outside
1383 || (rShapeType == u"ooxml-mathNotEqual" && nHandleIndex == 0) // -adj3 / 2 outside
1384 || (rShapeType == u"ooxml-star4") || (rShapeType == u"ooxml-star6")
1385 || (rShapeType == u"ooxml-star8") || (rShapeType == u"ooxml-star10")
1386 || (rShapeType == u"ooxml-star12") || (rShapeType == u"ooxml-star16")
1387 || (rShapeType == u"ooxml-star24") || (rShapeType == u"ooxml-star32"))
1388 return (fH / 2.0 - fY) / fH * 100000.0;
1390 // pattern (vc - y) / h * 200000
1391 if ((rShapeType == u"ooxml-leftArrow") || (rShapeType == u"ooxml-leftRightArrow")
1392 || (rShapeType == u"ooxml-mathDivide" && nHandleIndex == 0)
1393 || (rShapeType == u"ooxml-mathEqual" && nHandleIndex == 1)
1394 || (rShapeType == u"ooxml-mathMinus") || (rShapeType == u"ooxml-notchedRightArrow")
1395 || (rShapeType == u"ooxml-mathNotEqual" && nHandleIndex == 2)
1396 || (rShapeType == u"ooxml-quadArrowCallout" && nHandleIndex == 3)
1397 || (rShapeType == u"ooxml-rightArrow") || (rShapeType == u"ooxml-stripedRightArrow")
1398 || (rShapeType == u"ooxml-upDownArrowCallout" && nHandleIndex == 3))
1399 return (fH / 2.0 - fY) / fH * 200000.0;
1401 // pattern (y - vc) / h * 100000
1402 if ((rShapeType == u"ooxml-cloudCallout") || (rShapeType == u"ooxml-wedgeEllipseCallout")
1403 || (rShapeType == u"ooxml-wedgeRectCallout")
1404 || (rShapeType == u"ooxml-wedgeRoundRectCallout"))
1405 return (fY - fH / 2.0) / fH * 100000.0;
1407 // pattern (h - y) / h * 100000 or (b - y) / h * 100000
1408 if ((rShapeType == u"ooxml-ellipseRibbon" && nHandleIndex == 2)
1409 || (rShapeType == u"ooxml-ellipseRibbon2" && nHandleIndex == 0)
1410 || (rShapeType == u"ooxml-ribbon2")
1411 || (rShapeType == u"ooxml-upArrowCallout" && nHandleIndex == 3))
1412 return (fH - fY) / fH * 100000.0;
1414 // special pattern smiley
1415 if (rShapeType == u"ooxml-smileyFace")
1416 return (fY - fH * 16515.0 / 21600.0) / fH * 100000.0;
1418 // special pattern for star with odd number of tips, because center of star not center of shape
1419 if (rShapeType == u"ooxml-star5")
1420 return (fH / 2.0 - fY * 100000.0 / 110557.0) / fH * 100000.0;
1421 if (rShapeType == u"ooxml-star7")
1422 return (fH / 2.0 - fY * 100000.0 / 105210.0) / fH * 100000.0;
1424 // special pattern swooshArrow
1425 if (rShapeType == u"ooxml-swooshArrow")
1426 return (fY - std::min(fW, fH) / 8.0) / fH * 100000.0;
1428 // special pattern leftRightRibbon
1429 if (rShapeType == u"ooxml-leftRightRibbon")
1430 return fY / fH * 200000 - 100000;
1432 // pattern y / h * 100000, simple scaling
1433 if (o3tl::starts_with(rShapeType, u"ooxml-"))
1434 return fY / fH * 100000.0;
1436 return fY; // method is unknown
1439 static double lcl_getAngleInOOXMLUnit(double fDY, double fDX)
1441 if (fDX != 0.0 || fDY != 0.0)
1443 double fAngleRad(atan2(fDY, fDX));
1444 double fAngle = basegfx::rad2deg(fAngleRad);
1445 // atan2 returns angle in ]-pi; pi], OOXML preset shapes use [0;360[.
1446 if (fAngle < 0.0)
1447 fAngle += 360.0;
1448 // OOXML uses angle unit 1/60000 degree.
1449 fAngle *= 60000.0;
1450 return fAngle;
1452 return 0.0; // no angle defined for origin in polar coordinate system
1455 static double lcl_getRadiusDistance(double fWR, double fHR, double fX, double fY)
1457 // Get D so, that point (fX|fY) is on the ellipse, that has width fWR-D and
1458 // height fHR-D and center in origin.
1459 // Get solution of ellipse equation (fX/(fWR-D))^2 + (fY/(fHR-D)^2 = 1 by solving
1460 // fX^2*(fHR-D)^2 + fY^2*(fWR-D)^2 - (fWR-D)^2 * (fHR-D)^2 = 0 with Newton-method.
1461 if (fX == 0.0)
1462 return std::min(fHR - fY, fWR);
1463 else if (fY == 0.0)
1464 return std::min(fWR - fX, fHR);
1466 double fD = std::min(fWR, fHR) - std::hypot(fX, fY); // iteration start value
1467 sal_uInt8 nIter(0);
1468 bool bFound(false);
1471 ++nIter;
1472 const double fOldD(fD);
1473 const double fWRmD(fWR - fD);
1474 const double fHRmD(fHR - fD);
1475 double fNumerator
1476 = fX * fX * fHRmD * fHRmD + fY * fY * fWRmD * fWRmD - fWRmD * fWRmD * fHRmD * fHRmD;
1477 double fDenominator
1478 = 2.0 * (fHRmD * (fWRmD * fWRmD - fX * fX) + fWRmD * (fHRmD * fHRmD - fY * fY));
1479 if (fDenominator != 0.0)
1481 fD = fD - fNumerator / fDenominator;
1482 bFound = fabs(fOldD - fD) < 1.0E-12;
1484 else
1485 fD = fD * 0.9; // new start value
1486 } while (nIter < 50 && !bFound);
1487 return fD;
1490 bool EnhancedCustomShape2d::SetHandleControllerPosition( const sal_uInt32 nIndex, const css::awt::Point& rPosition )
1492 // The method name is misleading. Essentially it calculates the adjustment values from a given
1493 // handle position.
1495 // For ooxml-foo shapes, the way to calculate the adjustment value from the handle position depends on
1496 // the type of the shape, therefore need 'Type'.
1497 OUString sShapeType("non-primitive"); // default for ODF
1498 const SdrCustomShapeGeometryItem& rGeometryItem(mrSdrObjCustomShape.GetMergedItem( SDRATTR_CUSTOMSHAPE_GEOMETRY ));
1499 const Any* pAny = rGeometryItem.GetPropertyValueByName("Type");
1500 if (pAny)
1501 *pAny >>= sShapeType;
1503 bool bRetValue = false;
1504 if ( nIndex < GetHdlCount() )
1506 Handle aHandle;
1507 if ( ConvertSequenceToEnhancedCustomShape2dHandle( seqHandles[ nIndex ], aHandle ) )
1509 Point aP( rPosition.X, rPosition.Y );
1510 // apply the negative object rotation to the controller position
1512 aP.Move( -aLogicRect.Left(), -aLogicRect.Top() );
1513 if ( bFlipH )
1514 aP.setX( aLogicRect.GetWidth() - aP.X() );
1515 if ( bFlipV )
1516 aP.setY( aLogicRect.GetHeight() - aP.Y() );
1517 if ( nRotateAngle )
1519 double a = -toRadians(nRotateAngle);
1520 RotatePoint( aP, Point( aLogicRect.GetWidth() / 2, aLogicRect.GetHeight() / 2 ), sin( a ), cos( a ) );
1522 const GeoStat aGeoStat(mrSdrObjCustomShape.GetGeoStat());
1523 if ( aGeoStat.nShearAngle )
1525 double nTan = -aGeoStat.mfTanShearAngle;
1526 if (bFlipV != bFlipH)
1527 nTan = -nTan;
1528 ShearPoint( aP, Point( aLogicRect.GetWidth() / 2, aLogicRect.GetHeight() / 2 ), nTan );
1531 double fPos1 = aP.X(); //( bFlipH ) ? aLogicRect.GetWidth() - aP.X() : aP.X();
1532 double fPos2 = aP.Y(); //( bFlipV ) ? aLogicRect.GetHeight() -aP.Y() : aP.Y();
1533 fPos1 = !basegfx::fTools::equalZero(fXScale) ? (fPos1 / fXScale) : SAL_MAX_INT32;
1534 fPos2 = !basegfx::fTools::equalZero(fYScale) ? (fPos2 / fYScale) : SAL_MAX_INT32;
1535 // revert -nCoordLeft and -nCoordTop aus GetPoint()
1536 fPos1 += nCoordLeft;
1537 fPos2 += nCoordTop;
1539 // Used for scaling the adjustment values based on handle positions
1540 double fWidth;
1541 double fHeight;
1543 if ( nCoordWidth || nCoordHeight )
1545 fWidth = nCoordWidth;
1546 fHeight = nCoordHeight;
1548 else
1550 fWidth = aLogicRect.GetWidth();
1551 fHeight = aLogicRect.GetHeight();
1554 if ( aHandle.nFlags & HandleFlags::SWITCHED )
1556 if ( aLogicRect.GetHeight() > aLogicRect.GetWidth() )
1558 double fX = fPos1;
1559 double fY = fPos2;
1560 double fTmp = fWidth;
1561 fPos1 = fY;
1562 fPos2 = fX;
1563 fHeight = fWidth;
1564 fWidth = fTmp;
1568 sal_Int32 nFirstAdjustmentValue = -1, nSecondAdjustmentValue = -1;
1570 // ODF shapes are expected to use a direct binding between position and adjustment
1571 // values. OOXML preset shapes use known formulas. These are calculated backward to
1572 // get the adjustment values. So far we do not have a general method to calculate
1573 // the adjustment values for any shape from the handle position.
1574 if ( aHandle.aPosition.First.Type == EnhancedCustomShapeParameterType::ADJUSTMENT )
1575 aHandle.aPosition.First.Value >>= nFirstAdjustmentValue;
1576 if ( aHandle.aPosition.Second.Type == EnhancedCustomShapeParameterType::ADJUSTMENT )
1577 aHandle.aPosition.Second.Value>>= nSecondAdjustmentValue;
1579 if ( aHandle.nFlags & ( HandleFlags::POLAR | HandleFlags::REFR | HandleFlags::REFANGLE))
1580 { // Polar-Handle
1582 if (aHandle.nFlags & HandleFlags::REFR)
1583 nFirstAdjustmentValue = aHandle.nRefR;
1584 if (aHandle.nFlags & HandleFlags::REFANGLE)
1585 nSecondAdjustmentValue = aHandle.nRefAngle;
1587 double fAngle(0.0);
1588 double fRadius(0.0);
1589 // 'then' treats only shapes of type "ooxml-foo", fontwork shapes have been mapped
1590 // to MS binary import and will be treated in 'else'.
1591 if (bOOXMLShape)
1593 // DrawingML polar handles set REFR or REFANGLE instead of POLAR
1594 // use the shape center instead.
1595 double fDX = fPos1 - fWidth / 2.0;
1596 double fDY = fPos2 - fHeight / 2.0;
1598 // There exists no common pattern. 'radius' or 'angle' might have special meaning.
1599 if (sShapeType == "ooxml-blockArc" && nIndex == 1)
1601 // usual angle, special radius
1602 fAngle = lcl_getAngleInOOXMLUnit(fDY, fDX);
1603 // The value connected to REFR is the _difference_ between the outer
1604 // ellipse given by shape width and height and the inner ellipse through
1605 // the handle position.
1606 double fRadiusDifference
1607 = lcl_getRadiusDistance(fWidth / 2.0, fHeight / 2.0, fDX, fDY);
1608 double fss(std::min(fWidth, fHeight));
1609 if (fss != 0)
1610 fRadius = fRadiusDifference * 100000.0 / fss;
1612 else if (sShapeType == "ooxml-donut" || sShapeType == "ooxml-noSmoking")
1614 // no angle adjustment, radius bound to x-coordinate of handle
1615 double fss(std::min(fWidth, fHeight));
1616 if (fss != 0.0)
1617 fRadius = fPos1 * 100000.0 / fss;
1619 else if ((sShapeType == "ooxml-circularArrow"
1620 || sShapeType == "ooxml-leftRightCircularArrow"
1621 || sShapeType == "ooxml-leftCircularArrow")
1622 && nIndex == 0)
1624 // The value adj2 is the increase compared to the angle in adj3
1625 double fHandleAngle = lcl_getAngleInOOXMLUnit(fDY, fDX);
1626 if (sShapeType == "ooxml-leftCircularArrow")
1627 fAngle = GetAdjustValueAsDouble(2) - fHandleAngle;
1628 else
1629 fAngle = fHandleAngle - GetAdjustValueAsDouble(2);
1630 if (fAngle < 0.0) // 0deg to 360deg cut
1631 fAngle += 21600000.0;
1632 // no REFR
1634 else if ((sShapeType == "ooxml-circularArrow"
1635 || sShapeType == "ooxml-leftCircularArrow"
1636 || sShapeType == "ooxml-leftRightCircularArrow")
1637 && nIndex == 2)
1639 // The value adj1 connected to REFR is the thickness of the arc. The adjustvalue adj5
1640 // has the _difference_ between the outer ellipse given by shape width and height
1641 // and the middle ellipse of the arc. The handle is on the outer side of the
1642 // arc. So we calculate the difference between the ellipse through the handle
1643 // and the outer ellipse and subtract then.
1644 double fRadiusDifferenceHandle
1645 = lcl_getRadiusDistance(fWidth / 2.0, fHeight / 2.0, fDX, fDY);
1646 double fadj5(GetAdjustValueAsDouble(4));
1647 double fss(std::min(fWidth, fHeight));
1648 if (fss != 0.0)
1650 fadj5 = fadj5 * fss / 100000.0;
1651 fRadius = 2.0 * (fadj5 - fRadiusDifferenceHandle);
1652 fRadius = fRadius * 100000.0 / fss;
1654 // ToDo: Get angle adj3 exact. Use approximation for now
1655 fAngle = lcl_getAngleInOOXMLUnit(fDY, fDX);
1657 else if ((sShapeType == "ooxml-circularArrow"
1658 || sShapeType == "ooxml-leftCircularArrow"
1659 || sShapeType == "ooxml-leftRightCircularArrow")
1660 && nIndex == 3)
1662 // ToDo: Getting handle position from adjustment value adj5 is complex.
1663 // Analytical or numerical solution for backward calculation is missing.
1664 // Approximation for now, using a line from center through handle position.
1665 double fAngleRad(0.0);
1666 if (fDX != 0.0 || fDY != 0.0)
1667 fAngleRad = atan2(fDY, fDX);
1668 double fHelpX = cos(fAngleRad) * fHeight / 2.0;
1669 double fHelpY = sin(fAngleRad) * fWidth / 2.0;
1670 if (fHelpX != 0.0 || fHelpY != 0.0)
1672 double fHelpAngle = atan2(fHelpY, fHelpX);
1673 double fOuterX = fWidth / 2.0 * cos(fHelpAngle);
1674 double fOuterY = fHeight / 2.0 * sin(fHelpAngle);
1675 double fOuterRadius = std::hypot(fOuterX, fOuterY);
1676 double fHandleRadius = std::hypot(fDX, fDY);
1677 fRadius = (fOuterRadius - fHandleRadius) / 2.0;
1678 double fss(std::min(fWidth, fHeight));
1679 if (fss != 0.0)
1680 fRadius = fRadius * 100000.0 / fss;
1682 // no REFANGLE
1684 else if (sShapeType == "ooxml-mathNotEqual" && nIndex == 1)
1686 double fadj1(GetAdjustValueAsDouble(0));
1687 double fadj3(GetAdjustValueAsDouble(2));
1688 fadj1 = fadj1 * fHeight / 100000.0;
1689 fadj3 = fadj3 * fHeight / 100000.0;
1690 double fDYRefHorizBar = fDY + fadj1 + fadj3;
1691 if (fDX != 0.0 || fDYRefHorizBar != 0.0)
1693 double fRawAngleDeg = basegfx::rad2deg(atan2(fDYRefHorizBar, fDX));
1694 fAngle = (fRawAngleDeg + 180.0) * 60000.0;
1696 // no REFR
1698 else
1700 // no special meaning of radius or angle, suitable for "ooxml-arc",
1701 // "ooxml-chord", "ooxml-pie" and circular arrows value adj4.
1702 fAngle = lcl_getAngleInOOXMLUnit(fDY, fDX);
1703 fRadius = std::hypot(fDX, fDY);
1704 double fss(std::min(fWidth, fHeight));
1705 if (fss != 0.0)
1706 fRadius = fRadius * 100000.0 / fss;
1709 else // e.g. shapes from ODF, MS binary import or shape type "fontwork-foo"
1711 double fXRef, fYRef;
1712 if (aHandle.nFlags & HandleFlags::POLAR)
1714 GetParameter(fXRef, aHandle.aPolar.First, false, false);
1715 GetParameter(fYRef, aHandle.aPolar.Second, false, false);
1717 else
1719 fXRef = fWidth / 2.0;
1720 fYRef = fHeight / 2.0;
1722 const double fDX = fPos1 - fXRef;
1723 const double fDY = fPos2 - fYRef;
1724 // ToDo: MS binary uses fixed-point number for the angle. Make sure conversion
1725 // to double is done in import and export.
1726 // ToDo: Angle unit is degree, but range ]-180;180] or [0;360[? Assume ]-180;180].
1727 if (fDX != 0.0 || fDY != 0.0)
1729 fRadius = std::hypot(fDX, fDY);
1730 fAngle = basegfx::rad2deg(atan2(fDY, fDX));
1734 // All formats can restrict the radius to a range
1735 if ( aHandle.nFlags & HandleFlags::RADIUS_RANGE_MINIMUM )
1737 double fMin;
1738 GetParameter( fMin, aHandle.aRadiusRangeMinimum, false, false );
1739 if ( fRadius < fMin )
1740 fRadius = fMin;
1742 if ( aHandle.nFlags & HandleFlags::RADIUS_RANGE_MAXIMUM )
1744 double fMax;
1745 GetParameter( fMax, aHandle.aRadiusRangeMaximum, false, false );
1746 if ( fRadius > fMax )
1747 fRadius = fMax;
1750 if ( nFirstAdjustmentValue >= 0 )
1751 SetAdjustValueAsDouble( fRadius, nFirstAdjustmentValue );
1752 if ( nSecondAdjustmentValue >= 0 )
1753 SetAdjustValueAsDouble( fAngle, nSecondAdjustmentValue );
1755 else // XY-Handle
1757 // Calculating the adjustment values follows in most cases some patterns, which only
1758 // need width and height of the shape and handle position. These patterns are calculated
1759 // in the static, local methods. More complex calculations or additional steps are
1760 // done here.
1761 // Values for corner cases like 'root(negative)' or 'div zero' are meaningless dummies.
1762 // Identifiers often refer to guide names in OOXML shape definitions.
1763 double fAdjustX = fPos1;
1764 double fAdjustY = fPos2;
1765 if (aHandle.nFlags & HandleFlags::REFX)
1767 nFirstAdjustmentValue = aHandle.nRefX;
1768 if ((sShapeType == "ooxml-gear6") || (sShapeType == "ooxml-gear9"))
1770 // special, needs angle calculations
1771 double fss(std::min(fWidth, fHeight));
1772 double fadj1(GetAdjustValueAsDouble(0)); // from point D6 or D9
1773 double fth(fadj1 * fss / 100000.0); // radius difference
1774 double frw(fWidth / 2.0 - fth); // inner ellipse
1775 double frh(fHeight / 2.0 - fth);
1776 double fDX(fPos1 - fWidth / 2.0);
1777 double fDY(fPos2 - fHeight / 2.0);
1778 double fbA(-1.7); // effective angle for point A6 or A9, dummy value
1779 if (fDX != 0.0 || fDY != 0.0)
1780 fbA = atan2(fDY, fDX);
1781 double faA(fbA); // corresponding circle angle, dummy value
1782 double ftmpX(frh * cos(fbA));
1783 double ftmpY(frw * sin(fbA));
1784 if (ftmpX != 0.0 || ftmpY != 0.0)
1785 faA = atan2(ftmpY, ftmpX); // range ]-pi..pi], here -pi < faA < -pi/2
1786 // screen 270 deg = mathematic coordinate system -pi/2
1787 double fha(-M_PI_2 - faA); // positive circle angle difference to 270 deg
1788 if (abs(fha) == M_PI_2) // should not happen, but ensure no tan(90deg)
1789 fha = 0.12; // dummy value
1790 double flFD(2 * std::min(frw, frh) * tan(fha) - fth);
1791 if (fss != 0.0)
1792 fAdjustX = flFD / fss * 100000.0;
1794 else
1796 fAdjustX
1797 = lcl_getXAdjustmentValue(sShapeType, nIndex, fPos1, fWidth, fHeight);
1798 if ((sShapeType == "ooxml-curvedDownArrow")
1799 || (sShapeType == "ooxml-curvedUpArrow"))
1801 double fss(std::min(fWidth, fHeight));
1802 if (fss != 0.0)
1804 double fadj3(GetAdjustValueAsDouble(2));
1805 double fHScaled(100000.0 * fHeight / fss);
1806 double fRadicand(fHScaled * fHScaled - fadj3 * fadj3);
1807 double fSqrt = fRadicand >= 0.0 ? sqrt(fRadicand) : 0.0;
1808 double fPart(200000.0 * fWidth / fss * (fSqrt + fHScaled));
1809 fAdjustX = fPart - 4.0 * fHScaled * fAdjustX;
1810 if (nIndex == 0)
1812 // calculate adj1
1813 double fadj2(GetAdjustValueAsDouble(1));
1814 fAdjustX = fAdjustX - fadj2 * (fSqrt + fHScaled);
1815 double fDenominator(fSqrt - 3.0 * fHScaled);
1816 fAdjustX /= fDenominator != 0.0 ? fDenominator : 1.0;
1818 else
1820 // nIndex == 1, calculate adj2
1821 double fadj1(GetAdjustValueAsDouble(0));
1822 fAdjustX = fAdjustX - fadj1 * (fSqrt - fHScaled);
1823 double fDenominator(fSqrt + 3.0 * fHScaled);
1824 fAdjustX /= fDenominator != 0.0 ? fDenominator : 1.0;
1831 if (aHandle.nFlags & HandleFlags::REFY)
1833 nSecondAdjustmentValue = aHandle.nRefY;
1834 if ((sShapeType == "ooxml-gear6") || (sShapeType == "ooxml-gear9"))
1836 // special, acts more like a polar handle radius
1837 double fDX = fPos1 - fWidth / 2.0;
1838 double fDY = fPos2 - fHeight / 2.0;
1839 double fRadiusDifference
1840 = lcl_getRadiusDistance(fWidth / 2.0, fHeight / 2.0, fDX, fDY);
1841 double fss(std::min(fWidth, fHeight));
1842 if (fss != 0)
1843 fAdjustY = fRadiusDifference / fss * 100000.0;
1845 else
1847 fAdjustY
1848 = lcl_getYAdjustmentValue(sShapeType, nIndex, fPos2, fWidth, fHeight);
1849 if (sShapeType == "ooxml-mathDivide" && nIndex == 1)
1850 fAdjustY = fAdjustY - GetAdjustValueAsDouble(0) / 2.0
1851 - GetAdjustValueAsDouble(2);
1852 else if (sShapeType == "ooxml-mathEqual" && nIndex == 0)
1853 fAdjustY -= GetAdjustValueAsDouble(1) / 2.0;
1854 else if (sShapeType == "ooxml-mathNotEqual" && nIndex == 0)
1855 fAdjustY -= GetAdjustValueAsDouble(2) / 2.0;
1856 else if (sShapeType == "ooxml-leftUpArrow" && nIndex == 0)
1857 fAdjustY -= GetAdjustValueAsDouble(1) * 2.0;
1858 else if ((sShapeType == "ooxml-curvedRightArrow")
1859 || (sShapeType == "ooxml-curvedLeftArrow"))
1861 double fss(std::min(fWidth, fHeight));
1862 if (fss != 0.0)
1864 double fadj3(GetAdjustValueAsDouble(2));
1865 double fWScaled(100000.0 * fWidth / fss);
1866 double fRadicand(fWScaled * fWScaled - fadj3 * fadj3);
1867 double fSqrt = fRadicand >= 0.0 ? sqrt(fRadicand) : 0.0;
1868 if (nIndex == 0)
1870 // calculate adj1
1871 double fadj2(GetAdjustValueAsDouble(1));
1872 fAdjustY = fWScaled * (2.0 * fAdjustY - fadj2);
1873 fAdjustY += (200000.0 / fss * fHeight - fadj2) * fSqrt;
1874 double fDenominator(fSqrt + fWScaled);
1875 fAdjustY /= fDenominator != 0.0 ? fDenominator : 1.0;
1877 else
1879 // nIndex == 1, calculate adj2
1880 double fadj1(GetAdjustValueAsDouble(0));
1881 fAdjustY = fWScaled * (2.0 * fAdjustY + fadj1);
1882 fAdjustY += (200000.0 / fss * fHeight - fadj1) * fSqrt;
1883 double fDenominator(fSqrt + 3.0 * fWScaled);
1884 fAdjustY /= fDenominator != 0.0 ? fDenominator : 1.0;
1888 else if (sShapeType == "ooxml-uturnArrow" && nIndex == 2)
1890 double fss(std::min(fWidth, fHeight));
1891 if (fss != 0.0)
1893 double fadj5(GetAdjustValueAsDouble(4));
1894 fAdjustY += fHeight / fss * (fadj5 - 100000.0);
1897 else if (sShapeType == "ooxml-leftRightRibbon")
1899 if (nIndex == 0)
1900 fAdjustY = GetAdjustValueAsDouble(2) - fAdjustY;
1901 else // nIndex == 2
1902 fAdjustY = GetAdjustValueAsDouble(0) + fAdjustY;
1907 if ( nFirstAdjustmentValue >= 0 )
1909 if ( aHandle.nFlags & HandleFlags::RANGE_X_MINIMUM ) // check if horizontal handle needs to be within a range
1911 double fXMin;
1912 GetParameter( fXMin, aHandle.aXRangeMinimum, false, false );
1913 if (fAdjustX < fXMin)
1914 fAdjustX = fXMin;
1916 if ( aHandle.nFlags & HandleFlags::RANGE_X_MAXIMUM ) // check if horizontal handle needs to be within a range
1918 double fXMax;
1919 GetParameter( fXMax, aHandle.aXRangeMaximum, false, false );
1920 if (fAdjustX > fXMax)
1921 fAdjustX = fXMax;
1923 SetAdjustValueAsDouble(fAdjustX, nFirstAdjustmentValue);
1925 if ( nSecondAdjustmentValue >= 0 )
1927 if ( aHandle.nFlags & HandleFlags::RANGE_Y_MINIMUM ) // check if vertical handle needs to be within a range
1929 double fYMin;
1930 GetParameter( fYMin, aHandle.aYRangeMinimum, false, false );
1931 if (fAdjustY < fYMin)
1932 fAdjustY = fYMin;
1934 if ( aHandle.nFlags & HandleFlags::RANGE_Y_MAXIMUM ) // check if vertical handle needs to be within a range
1936 double fYMax;
1937 GetParameter( fYMax, aHandle.aYRangeMaximum, false, false );
1938 if (fAdjustY > fYMax)
1939 fAdjustY = fYMax;
1941 SetAdjustValueAsDouble(fAdjustY, nSecondAdjustmentValue);
1944 // and writing them back into the GeometryItem
1945 SdrCustomShapeGeometryItem aGeometryItem(mrSdrObjCustomShape.GetMergedItem( SDRATTR_CUSTOMSHAPE_GEOMETRY ));
1946 css::beans::PropertyValue aPropVal;
1947 aPropVal.Name = "AdjustmentValues";
1948 aPropVal.Value <<= seqAdjustmentValues;
1949 aGeometryItem.SetPropertyValue( aPropVal );
1950 mrSdrObjCustomShape.SetMergedItem( aGeometryItem );
1951 bRetValue = true;
1954 return bRetValue;
1957 void EnhancedCustomShape2d::SwapStartAndEndArrow( SdrObject* pObj ) //#108274
1959 XLineStartItem aLineStart;
1960 aLineStart.SetLineStartValue(pObj->GetMergedItem( XATTR_LINEEND ).GetLineEndValue());
1961 XLineStartWidthItem aLineStartWidth(pObj->GetMergedItem( XATTR_LINEENDWIDTH ).GetValue());
1962 XLineStartCenterItem aLineStartCenter(pObj->GetMergedItem( XATTR_LINEENDCENTER ).GetValue());
1964 XLineEndItem aLineEnd;
1965 aLineEnd.SetLineEndValue(pObj->GetMergedItem( XATTR_LINESTART ).GetLineStartValue());
1966 XLineEndWidthItem aLineEndWidth(pObj->GetMergedItem( XATTR_LINESTARTWIDTH ).GetValue());
1967 XLineEndCenterItem aLineEndCenter(pObj->GetMergedItem( XATTR_LINESTARTCENTER ).GetValue());
1969 pObj->SetMergedItem( aLineStart );
1970 pObj->SetMergedItem( aLineStartWidth );
1971 pObj->SetMergedItem( aLineStartCenter );
1972 pObj->SetMergedItem( aLineEnd );
1973 pObj->SetMergedItem( aLineEndWidth );
1974 pObj->SetMergedItem( aLineEndCenter );
1977 static basegfx::B2DPolygon CreateArc( const tools::Rectangle& rRect, const Point& rStart, const Point& rEnd, const bool bClockwise )
1979 tools::Rectangle aRect( rRect );
1980 Point aStart( rStart );
1981 Point aEnd( rEnd );
1983 sal_Int32 bSwapStartEndAngle = 0;
1985 if ( aRect.Left() > aRect.Right() )
1986 bSwapStartEndAngle ^= 0x01;
1987 if ( aRect.Top() > aRect.Bottom() )
1988 bSwapStartEndAngle ^= 0x11;
1989 if ( bSwapStartEndAngle )
1991 aRect.Normalize();
1992 if ( bSwapStartEndAngle & 1 )
1994 Point aTmp( aStart );
1995 aStart = aEnd;
1996 aEnd = aTmp;
2000 tools::Polygon aTempPoly( aRect, aStart, aEnd, PolyStyle::Arc );
2001 basegfx::B2DPolygon aRetval;
2003 if ( bClockwise )
2005 for ( sal_uInt16 j = aTempPoly.GetSize(); j--; )
2007 aRetval.append(basegfx::B2DPoint(aTempPoly[ j ].X(), aTempPoly[ j ].Y()));
2010 else
2012 for ( sal_uInt16 j = 0; j < aTempPoly.GetSize(); j++ )
2014 aRetval.append(basegfx::B2DPoint(aTempPoly[ j ].X(), aTempPoly[ j ].Y()));
2018 return aRetval;
2021 static double lcl_getNormalizedCircleAngleRad(const double fWR, const double fHR, const double fEllipseAngleDeg)
2023 double fRet(0.0);
2024 double fEAngleDeg(fmod(fEllipseAngleDeg, 360.0));
2025 if (fEAngleDeg < 0.0)
2026 fEAngleDeg += 360.0;
2027 if (fEAngleDeg == 0.0 || fEAngleDeg == 90.0 || fEAngleDeg == 180.0 || fEAngleDeg == 270.0)
2028 return basegfx::deg2rad(fEAngleDeg);
2029 const double fX(fHR * cos(basegfx::deg2rad(fEAngleDeg)));
2030 const double fY(fWR * sin(basegfx::deg2rad(fEAngleDeg)));
2031 if (fX != 0.0 || fY != 0.0)
2033 fRet = atan2(fY, fX);
2034 if (fRet < 0.0)
2035 fRet += 2 * M_PI;
2037 return fRet;
2040 static double lcl_getNormalizedAngleRad(const double fCircleAngleDeg)
2042 double fRet(fmod(fCircleAngleDeg, 360.0));
2043 if (fRet < 0.0)
2044 fRet += 360.0;
2045 return basegfx::deg2rad(fRet);
2048 void EnhancedCustomShape2d::CreateSubPath(
2049 sal_Int32& rSrcPt,
2050 sal_Int32& rSegmentInd,
2051 std::vector< std::pair< rtl::Reference<SdrPathObj>, double> >& rObjectList,
2052 const bool bLineGeometryNeededOnly,
2053 const bool bSortFilledObjectsToBack,
2054 sal_Int32 nIndex)
2056 bool bNoFill = false;
2057 bool bNoStroke = false;
2058 double dBrightness = 0.0; //no blending
2060 basegfx::B2DPolyPolygon aNewB2DPolyPolygon;
2061 basegfx::B2DPolygon aNewB2DPolygon;
2063 SetPathSize( nIndex );
2065 sal_Int32 nSegInfoSize = seqSegments.getLength();
2066 if ( !nSegInfoSize )
2068 for ( const EnhancedCustomShapeParameterPair& rCoordinate : std::as_const(seqCoordinates) )
2070 const Point aTempPoint(GetPoint( rCoordinate, true, true ));
2071 aNewB2DPolygon.append(basegfx::B2DPoint(aTempPoint.X(), aTempPoint.Y()));
2074 aNewB2DPolygon.setClosed(true);
2076 else
2078 sal_Int32 nCoordSize = seqCoordinates.getLength();
2079 for ( ;rSegmentInd < nSegInfoSize; )
2081 sal_Int16 nCommand = seqSegments[ rSegmentInd ].Command;
2082 sal_Int16 nPntCount= seqSegments[ rSegmentInd++ ].Count;
2084 switch ( nCommand )
2086 case NOFILL :
2087 bNoFill = true;
2088 break;
2089 case NOSTROKE :
2090 bNoStroke = true;
2091 break;
2092 case DARKEN :
2093 dBrightness = -0.4; //use sign to distinguish DARKEN from LIGHTEN
2094 break;
2095 case DARKENLESS :
2096 dBrightness = -0.2;
2097 break;
2098 case LIGHTEN :
2099 dBrightness = 0.4;
2100 break;
2101 case LIGHTENLESS :
2102 dBrightness = 0.2;
2103 break;
2104 case MOVETO :
2106 if(aNewB2DPolygon.count() > 1)
2108 // #i76201# Add conversion to closed polygon when first and last points are equal
2109 basegfx::utils::checkClosed(aNewB2DPolygon);
2110 aNewB2DPolyPolygon.append(aNewB2DPolygon);
2113 aNewB2DPolygon.clear();
2115 if ( rSrcPt < nCoordSize )
2117 const Point aTempPoint(GetPoint( seqCoordinates[ rSrcPt++ ], true, true ));
2118 SAL_INFO(
2119 "svx",
2120 "moveTo: " << aTempPoint.X() << ","
2121 << aTempPoint.Y());
2122 aNewB2DPolygon.append(basegfx::B2DPoint(aTempPoint.X(), aTempPoint.Y()));
2125 break;
2126 case ENDSUBPATH :
2127 break;
2128 case CLOSESUBPATH :
2130 if(aNewB2DPolygon.count())
2132 if(aNewB2DPolygon.count() > 1)
2134 aNewB2DPolygon.setClosed(true);
2135 aNewB2DPolyPolygon.append(aNewB2DPolygon);
2138 aNewB2DPolygon.clear();
2141 break;
2142 case CURVETO :
2144 for ( sal_uInt16 i = 0; ( i < nPntCount ) && ( ( rSrcPt + 2 ) < nCoordSize ); i++ )
2146 const Point aControlA(GetPoint( seqCoordinates[ rSrcPt++ ], true, true ));
2147 const Point aControlB(GetPoint( seqCoordinates[ rSrcPt++ ], true, true ));
2148 const Point aEnd(GetPoint( seqCoordinates[ rSrcPt++ ], true, true ));
2150 DBG_ASSERT(aNewB2DPolygon.count(), "EnhancedCustomShape2d::CreateSubPath: Error in adding control point (!)");
2151 aNewB2DPolygon.appendBezierSegment(
2152 basegfx::B2DPoint(aControlA.X(), aControlA.Y()),
2153 basegfx::B2DPoint(aControlB.X(), aControlB.Y()),
2154 basegfx::B2DPoint(aEnd.X(), aEnd.Y()));
2157 break;
2159 case ANGLEELLIPSE: // command U
2160 case ANGLEELLIPSETO: // command T
2162 // Some shapes will need special handling, decide on property 'Type'.
2163 OUString sShpType;
2164 SdrCustomShapeGeometryItem& rGeometryItem = const_cast<SdrCustomShapeGeometryItem&>(mrSdrObjCustomShape.GetMergedItem(SDRATTR_CUSTOMSHAPE_GEOMETRY));
2165 Any* pAny = rGeometryItem.GetPropertyValueByName("Type");
2166 if (pAny)
2167 *pAny >>= sShpType;
2168 // User defined shapes in MS binary format, which contain command U or T after import
2169 // in LibreOffice, starts with "mso".
2170 const bool bIsFromBinaryImport(sShpType.startsWith("mso"));
2171 // The only own or imported preset shapes with U command are those listed below.
2172 // Command T is not used in preset shapes.
2173 const std::unordered_set<OUString> aPresetShapesWithU =
2174 { "ellipse", "ring", "smiley", "sun", "forbidden", "flowchart-connector",
2175 "flowchart-summing-junction", "flowchart-or", "cloud-callout"};
2176 std::unordered_set<OUString>::const_iterator aIter = aPresetShapesWithU.find(sShpType);
2177 const bool bIsPresetShapeWithU(aIter != aPresetShapesWithU.end());
2179 for (sal_uInt16 i = 0; (i < nPntCount) && ((rSrcPt + 2) < nCoordSize); i++)
2181 // ANGLEELLIPSE is the same as ANGLEELLIPSETO, only that it
2182 // makes an implicit MOVETO. That ends the previous subpath.
2183 if (ANGLEELLIPSE == nCommand)
2185 if (aNewB2DPolygon.count() > 1)
2187 // #i76201# Add conversion to closed polygon when first and last points are equal
2188 basegfx::utils::checkClosed(aNewB2DPolygon);
2189 aNewB2DPolyPolygon.append(aNewB2DPolygon);
2191 aNewB2DPolygon.clear();
2194 // Read all parameters, but do not finally handle them.
2195 basegfx::B2DPoint aCenter(GetPointAsB2DPoint(seqCoordinates[ rSrcPt ], true, true));
2196 double fWR; // horizontal ellipse radius
2197 double fHR; // vertical ellipse radius
2198 GetParameter(fWR, seqCoordinates[rSrcPt + 1].First, true, false);
2199 GetParameter(fHR, seqCoordinates[rSrcPt + 1].Second, false, true);
2200 double fStartAngle;
2201 GetParameter(fStartAngle, seqCoordinates[rSrcPt + 2].First, false, false);
2202 double fEndAngle;
2203 GetParameter(fEndAngle, seqCoordinates[rSrcPt + 2].Second, false, false);
2204 // Increasing here allows flat case differentiation tree by using 'continue'.
2205 rSrcPt += 3;
2207 double fScaledWR(fWR * fXScale);
2208 double fScaledHR(fHR * fYScale);
2209 if (fScaledWR == 0.0 && fScaledHR == 0.0)
2211 // degenerated ellipse, add center point
2212 aNewB2DPolygon.append(aCenter);
2213 continue;
2216 if (bIsFromBinaryImport)
2218 // If a shape comes from MS binary ('escher') import, the angles are in degrees*2^16
2219 // and the second angle is not an end angle, but a swing angle.
2220 // MS Word shows this behavior: 0deg right, 90deg top, 180deg left and 270deg
2221 // bottom. Third and forth parameter are horizontal and vertical radius, not width
2222 // and height as noted in VML spec. A positive swing angle goes counter-clock
2223 // wise (in user view). The swing angle might go several times around in case
2224 // abs(swing angle) >= 360deg. Stroke accumulates, so that e.g. dash-dot might fill the
2225 // gaps of previous turn. Fill does not accumulate but uses even-odd rule, semi-transparent
2226 // fill does not become darker. The start and end points of the arc are calculated by
2227 // using the angles on a circle and then scaling the circle to the ellipse. Caution, that
2228 // is different from angle handling in ARCANGLETO and ODF.
2229 // The following implementation generates such rendering. It is only for rendering legacy
2230 // MS shapes and independent of the meaning of commands U and T in ODF specification.
2232 // The WordArt shape 'RingOutside' has already angles in degree, all other need
2233 // conversion from fixed-point number.
2234 double fSwingAngle = fEndAngle;
2235 if (sShpType != "mso-spt143")
2237 fStartAngle /= 65536.0;
2238 fSwingAngle = fEndAngle / 65536.0;
2240 // Convert orientation
2241 fStartAngle = -fStartAngle;
2242 fSwingAngle = -fSwingAngle;
2244 fEndAngle = fStartAngle + fSwingAngle;
2245 if (fSwingAngle < 0.0)
2246 std::swap(fStartAngle, fEndAngle);
2247 double fFrom(fStartAngle);
2248 double fTo(fFrom + 180.0);
2249 basegfx::B2DPolygon aTempB2DPolygon;
2250 double fS; // fFrom in radians in [0..2Pi[
2251 double fE; // fTo or fEndAngle in radians in [0..2PI[
2252 while (fTo < fEndAngle)
2254 fS = lcl_getNormalizedAngleRad(fFrom);
2255 fE = lcl_getNormalizedAngleRad(fTo);
2256 aTempB2DPolygon.append(basegfx::utils::createPolygonFromEllipseSegment(aCenter, fScaledWR, fScaledHR, fS,fE));
2257 fFrom = fTo;
2258 fTo += 180.0;
2260 fS = lcl_getNormalizedAngleRad(fFrom);
2261 fE = lcl_getNormalizedAngleRad(fEndAngle);
2262 aTempB2DPolygon.append(basegfx::utils::createPolygonFromEllipseSegment(aCenter, fScaledWR, fScaledHR,fS, fE));
2263 if (fSwingAngle < 0)
2264 aTempB2DPolygon.flip();
2265 aNewB2DPolygon.append(aTempB2DPolygon);
2266 continue;
2269 // The not yet handled shapes are own preset shapes, or preset shapes from MS binary import, or user
2270 // defined shapes, or foreign shapes. Shapes from OOXML import do not use ANGLEELLIPSE or
2271 // ANGLEELLIPSETO, but use ARCANGLETO.
2272 if (bIsPresetShapeWithU)
2274 // Besides "cloud-callout" all preset shapes have angle values '0 360'.
2275 // The imported "cloud-callout" has angle values '0 360' too, only our own "cloud-callout"
2276 // has values '0 23592960'. But that is fixedfloat and means 360*2^16. Thus all these shapes
2277 // have a full ellipse with start at 0deg.
2278 aNewB2DPolygon.append(basegfx::utils::createPolygonFromEllipse(aCenter, fScaledWR, fScaledHR));
2279 continue;
2282 // In all other cases, full ODF conform handling is necessary. ODF rules:
2283 // Third and forth parameter are horizontal and vertical radius.
2284 // An angle determines the start or end point of the segment by intersection of the second angle
2285 // leg with the ellipse. The first angle leg is always the positive x-axis. For the position
2286 // of the intersection points the angle is used modulo 360deg in range [0deg..360deg[.
2287 // The position of range [0deg..360deg[ is the same as in command ARCANGLETO, with 0deg right,
2288 // 90deg bottom, 180deg left and 270deg top. Only if abs(end angle - start angle) == 360 deg,
2289 // a full ellipse is drawn. The segment is always drawn clock wise (in user view) from start
2290 // point to end point. The end point of the segment becomes the new "current" point.
2292 if (fabs(fabs(fEndAngle - fStartAngle) - 360.0) < 1.0E-15)
2294 // draw full ellipse
2295 // Because createPolygonFromEllipseSegment cannot create full ellipse and
2296 // createPolygonFromEllipse has no varying starts, we use two half ellipses.
2297 const double fS(lcl_getNormalizedCircleAngleRad(fWR, fHR, fStartAngle));
2298 const double fH(lcl_getNormalizedCircleAngleRad(fWR, fHR, fStartAngle + 180.0));
2299 const double fE(lcl_getNormalizedCircleAngleRad(fWR, fHR, fEndAngle));
2300 aNewB2DPolygon.append(basegfx::utils::createPolygonFromEllipseSegment(aCenter, fScaledWR, fScaledHR, fS, fH));
2301 aNewB2DPolygon.append(basegfx::utils::createPolygonFromEllipseSegment(aCenter, fScaledWR, fScaledHR, fH, fE));
2302 continue;
2305 // remaining cases with central segment angle < 360
2306 double fS(lcl_getNormalizedCircleAngleRad(fWR, fHR, fStartAngle));
2307 double fE(lcl_getNormalizedCircleAngleRad(fWR, fHR, fEndAngle));
2308 aNewB2DPolygon.append(basegfx::utils::createPolygonFromEllipseSegment(aCenter, fScaledWR, fScaledHR, fS, fE));
2309 } // end for
2310 } // end case
2311 break;
2313 case QUADRATICCURVETO :
2315 for ( sal_Int32 i(0); ( i < nPntCount ) && ( rSrcPt + 1 < nCoordSize ); i++ )
2317 DBG_ASSERT(aNewB2DPolygon.count(), "EnhancedCustomShape2d::CreateSubPath: Error no previous point for Q (!)");
2318 if (aNewB2DPolygon.count() > 0)
2320 const basegfx::B2DPoint aPreviousEndPoint(aNewB2DPolygon.getB2DPoint(aNewB2DPolygon.count()-1));
2321 const basegfx::B2DPoint aControlQ(GetPointAsB2DPoint( seqCoordinates[ rSrcPt++ ], true, true ));
2322 const basegfx::B2DPoint aEnd(GetPointAsB2DPoint( seqCoordinates[ rSrcPt++ ], true, true ));
2323 const basegfx::B2DPoint aControlA((aPreviousEndPoint + (aControlQ * 2)) / 3);
2324 const basegfx::B2DPoint aControlB(((aControlQ * 2) + aEnd) / 3);
2325 aNewB2DPolygon.appendBezierSegment(aControlA, aControlB, aEnd);
2327 else // no previous point; ill structured path, but try to draw as much as possible
2329 rSrcPt++; // skip control point
2330 const basegfx::B2DPoint aEnd(GetPointAsB2DPoint( seqCoordinates[ rSrcPt++ ], true, true ));
2331 aNewB2DPolygon.append(aEnd);
2335 break;
2337 case LINETO :
2339 for ( sal_Int32 i(0); ( i < nPntCount ) && ( rSrcPt < nCoordSize ); i++ )
2341 const Point aTempPoint(GetPoint( seqCoordinates[ rSrcPt++ ], true, true ));
2342 SAL_INFO(
2343 "svx",
2344 "lineTo: " << aTempPoint.X() << ","
2345 << aTempPoint.Y());
2346 aNewB2DPolygon.append(basegfx::B2DPoint(aTempPoint.X(), aTempPoint.Y()));
2349 break;
2351 case ARC :
2352 case CLOCKWISEARC :
2353 case ARCTO :
2354 case CLOCKWISEARCTO :
2356 bool bClockwise = ( nCommand == CLOCKWISEARC ) || ( nCommand == CLOCKWISEARCTO );
2357 bool bImplicitMoveTo = (nCommand == ARC) || (nCommand == CLOCKWISEARC);
2358 sal_uInt32 nXor = bClockwise ? 3 : 2;
2359 for ( sal_uInt16 i = 0; ( i < nPntCount ) && ( ( rSrcPt + 3 ) < nCoordSize ); i++ )
2361 if (bImplicitMoveTo)
2363 if (aNewB2DPolygon.count() > 1)
2365 // #i76201# Add conversion to closed polygon when first and last
2366 // points are equal
2367 basegfx::utils::checkClosed(aNewB2DPolygon);
2368 aNewB2DPolyPolygon.append(aNewB2DPolygon);
2370 aNewB2DPolygon.clear();
2372 tools::Rectangle aRect = tools::Rectangle::Normalize( GetPoint( seqCoordinates[ rSrcPt ], true, true ), GetPoint( seqCoordinates[ rSrcPt + 1 ], true, true ) );
2373 if ( aRect.GetWidth() && aRect.GetHeight() )
2375 Point aStart( GetPoint( seqCoordinates[ static_cast<sal_uInt16>( rSrcPt + nXor ) ], true, true ) );
2376 Point aEnd( GetPoint( seqCoordinates[ static_cast<sal_uInt16>( rSrcPt + ( nXor ^ 1 ) ) ], true, true ) );
2377 aNewB2DPolygon.append(CreateArc( aRect, aStart, aEnd, bClockwise));
2379 rSrcPt += 4;
2382 break;
2384 case ARCANGLETO :
2386 double fWR, fHR; // in Shape coordinate system
2387 double fStartAngle, fSwingAngle; // in deg
2389 for ( sal_uInt16 i = 0; ( i < nPntCount ) && ( rSrcPt + 1 < nCoordSize ); i++ )
2391 basegfx::B2DPoint aTempPair;
2392 aTempPair = GetPointAsB2DPoint(seqCoordinates[static_cast<sal_uInt16>(rSrcPt)], false /*bScale*/, false /*bReplaceGeoSize*/);
2393 fWR = aTempPair.getX();
2394 fHR = aTempPair.getY();
2395 aTempPair = GetPointAsB2DPoint(seqCoordinates[static_cast<sal_uInt16>(rSrcPt + 1)], false /*bScale*/, false /*bReplaceGeoSize*/);
2396 fStartAngle = aTempPair.getX();
2397 fSwingAngle = aTempPair.getY();
2399 // tdf#122323 MS Office clamps the swing angle to [-360,360]. Such restriction
2400 // is neither in OOXML nor in ODF. Nevertheless, to be compatible we do it for
2401 // "ooxml-foo" shapes. Those shapes have their origin in MS Office.
2402 if (bOOXMLShape)
2404 fSwingAngle = std::clamp(fSwingAngle, -360.0, 360.0);
2407 SAL_INFO("svx", "ARCANGLETO scale: " << fWR << "x" << fHR << " angles: " << fStartAngle << "," << fSwingAngle);
2409 if (aNewB2DPolygon.count() > 0) // otherwise no "current point"
2411 // use similar methods as in command U
2412 basegfx::B2DPolygon aTempB2DPolygon;
2414 if (fWR == 0.0 && fHR == 0.0)
2416 // degenerated ellipse, add this one point
2417 aTempB2DPolygon.append(basegfx::B2DPoint(0.0, 0.0));
2419 else
2421 double fEndAngle = fStartAngle + fSwingAngle;
2422 // Generate arc with ellipse left|top = 0|0.
2423 basegfx::B2DPoint aCenter(fWR, fHR);
2424 if (fSwingAngle < 0.0)
2425 std::swap(fStartAngle, fEndAngle);
2426 double fS; // fFrom in radians in [0..2Pi[
2427 double fE; // fTo or fEndAngle in radians in [0..2PI[
2428 double fFrom(fStartAngle);
2429 // createPolygonFromEllipseSegment expects angles in [0..2PI[.
2430 if (fSwingAngle >= 360.0 || fSwingAngle <= -360.0)
2432 double fTo(fFrom + 180.0);
2433 while (fTo < fEndAngle)
2435 fS = lcl_getNormalizedCircleAngleRad(fWR, fHR, fFrom);
2436 fE = lcl_getNormalizedCircleAngleRad(fWR, fHR, fTo);
2437 aTempB2DPolygon.append(basegfx::utils::createPolygonFromEllipseSegment(aCenter, fWR, fHR, fS,fE));
2438 fFrom = fTo;
2439 fTo += 180.0;
2442 fS = lcl_getNormalizedCircleAngleRad(fWR, fHR, fFrom);
2443 fE = lcl_getNormalizedCircleAngleRad(fWR, fHR, fEndAngle);
2444 aTempB2DPolygon.append(basegfx::utils::createPolygonFromEllipseSegment(aCenter, fWR, fHR,fS, fE));
2445 if (fSwingAngle < 0)
2446 aTempB2DPolygon.flip();
2447 aTempB2DPolygon.removeDoublePoints();
2449 // Scale arc to 1/100mm
2450 basegfx::B2DHomMatrix aMatrix = basegfx::utils::createScaleB2DHomMatrix(fXScale, fYScale);
2451 aTempB2DPolygon.transform(aMatrix);
2453 // Now that we have the arc, move it to the "current point".
2454 basegfx::B2DPoint aCurrentPointB2D( aNewB2DPolygon.getB2DPoint(aNewB2DPolygon.count() - 1 ) );
2455 const double fDx(aCurrentPointB2D.getX() - aTempB2DPolygon.getB2DPoint(0).getX());
2456 const double fDy(aCurrentPointB2D.getY() - aTempB2DPolygon.getB2DPoint(0).getY());
2457 aMatrix = basegfx::utils::createTranslateB2DHomMatrix(fDx, fDy);
2458 aTempB2DPolygon.transform(aMatrix);
2459 aNewB2DPolygon.append(aTempB2DPolygon);
2462 rSrcPt += 2;
2465 break;
2467 case ELLIPTICALQUADRANTX :
2468 case ELLIPTICALQUADRANTY :
2470 if (nPntCount && (rSrcPt < nCoordSize))
2472 // The arc starts at the previous point and ends at the point given in the parameter.
2473 basegfx::B2DPoint aStart;
2474 basegfx::B2DPoint aEnd;
2475 sal_uInt16 i = 0;
2476 if (rSrcPt)
2478 aStart = GetPointAsB2DPoint(seqCoordinates[rSrcPt - 1], true, true);
2480 else
2481 { // no previous point, path is ill-structured. But we want to show as much as possible.
2482 // Thus make a moveTo to the point given as parameter and continue from there.
2483 aStart = GetPointAsB2DPoint(seqCoordinates[static_cast<sal_uInt16>(rSrcPt)], true, true);
2484 aNewB2DPolygon.append(aStart);
2485 rSrcPt++;
2486 i++;
2488 // If there are several points, then the direction changes with every point.
2489 bool bIsXDirection(nCommand == ELLIPTICALQUADRANTX);
2490 basegfx::B2DPolygon aArc;
2491 for ( ; ( i < nPntCount ) && ( rSrcPt < nCoordSize ); i++ )
2493 aEnd = GetPointAsB2DPoint(seqCoordinates[rSrcPt], true, true);
2494 basegfx::B2DPoint aCenter;
2495 double fRadiusX = fabs(aEnd.getX() - aStart.getX());
2496 double fRadiusY = fabs(aEnd.getY() - aStart.getY());
2497 if (bIsXDirection)
2499 aCenter = basegfx::B2DPoint(aStart.getX(),aEnd.getY());
2500 if (aEnd.getX()<aStart.getX())
2502 if (aEnd.getY()<aStart.getY()) // left, up
2504 aArc = basegfx::utils::createPolygonFromEllipseSegment(aCenter, fRadiusX, fRadiusY, M_PI_2, M_PI);
2506 else // left, down
2508 aArc = basegfx::utils::createPolygonFromEllipseSegment(aCenter, fRadiusX, fRadiusY, M_PI, 1.5*M_PI);
2509 aArc.flip();
2512 else // aEnd.getX()>=aStart.getX()
2514 if (aEnd.getY()<aStart.getY()) // right, up
2516 aArc = basegfx::utils::createPolygonFromEllipseSegment(aCenter, fRadiusX, fRadiusY, 0.0, M_PI_2);
2517 aArc.flip();
2519 else // right, down
2521 aArc = basegfx::utils::createPolygonFromEllipseSegment(aCenter, fRadiusX, fRadiusY, 1.5*M_PI, 2*M_PI);
2525 else // y-direction
2527 aCenter = basegfx::B2DPoint(aEnd.getX(),aStart.getY());
2528 if (aEnd.getX()<aStart.getX())
2530 if (aEnd.getY()<aStart.getY()) // up, left
2532 aArc = basegfx::utils::createPolygonFromEllipseSegment(aCenter, fRadiusX, fRadiusY, 1.5*M_PI, 2*M_PI);
2533 aArc.flip();
2535 else // down, left
2537 aArc = basegfx::utils::createPolygonFromEllipseSegment(aCenter, fRadiusX, fRadiusY, 0.0, M_PI_2);
2540 else // aEnd.getX()>=aStart.getX()
2542 if (aEnd.getY()<aStart.getY()) // up, right
2544 aArc = basegfx::utils::createPolygonFromEllipseSegment(aCenter, fRadiusX, fRadiusY, M_PI, 1.5*M_PI);
2546 else // down, right
2548 aArc = basegfx::utils::createPolygonFromEllipseSegment(aCenter, fRadiusX, fRadiusY, M_PI_2, M_PI);
2549 aArc.flip();
2553 aNewB2DPolygon.append(aArc);
2554 rSrcPt++;
2555 bIsXDirection = !bIsXDirection;
2556 aStart = aEnd;
2559 // else error in path syntax, do nothing
2561 break;
2563 #ifdef DBG_CUSTOMSHAPE
2564 case UNKNOWN :
2565 default :
2567 SAL_WARN( "svx", "CustomShapes::unknown PolyFlagValue :" << nCommand );
2569 break;
2570 #endif
2572 if ( nCommand == ENDSUBPATH )
2573 break;
2576 if ( rSegmentInd == nSegInfoSize )
2577 rSegmentInd++;
2579 if(aNewB2DPolygon.count() > 1)
2581 // #i76201# Add conversion to closed polygon when first and last points are equal
2582 basegfx::utils::checkClosed(aNewB2DPolygon);
2583 aNewB2DPolyPolygon.append(aNewB2DPolygon);
2586 if(!aNewB2DPolyPolygon.count())
2587 return;
2589 // #i37011#
2590 bool bForceCreateTwoObjects(false);
2592 if(!bSortFilledObjectsToBack && !aNewB2DPolyPolygon.isClosed() && !bNoStroke)
2594 bForceCreateTwoObjects = true;
2597 if(bLineGeometryNeededOnly)
2599 bForceCreateTwoObjects = true;
2600 bNoFill = true;
2601 bNoStroke = false;
2604 if(bForceCreateTwoObjects || bSortFilledObjectsToBack)
2606 if(bFilled && !bNoFill)
2608 basegfx::B2DPolyPolygon aClosedPolyPolygon(aNewB2DPolyPolygon);
2609 aClosedPolyPolygon.setClosed(true);
2610 rtl::Reference<SdrPathObj> pFill(new SdrPathObj(
2611 mrSdrObjCustomShape.getSdrModelFromSdrObject(),
2612 SdrObjKind::Polygon,
2613 std::move(aClosedPolyPolygon)));
2614 SfxItemSet aTempSet(*this);
2615 aTempSet.Put(makeSdrShadowItem(false));
2616 aTempSet.Put(XLineStyleItem(drawing::LineStyle_NONE));
2617 pFill->SetMergedItemSet(aTempSet);
2618 rObjectList.push_back(std::pair< rtl::Reference<SdrPathObj>, double >(std::move(pFill), dBrightness));
2621 if(!bNoStroke)
2623 // there is no reason to use OBJ_PLIN here when the polygon is actually closed,
2624 // the non-fill is defined by XFILL_NONE. Since SdrPathObj::ImpForceKind() needs
2625 // to correct the polygon (here: open it) using the type, the last edge may get lost.
2626 // Thus, use a type that fits the polygon
2627 rtl::Reference<SdrPathObj> pStroke(new SdrPathObj(
2628 mrSdrObjCustomShape.getSdrModelFromSdrObject(),
2629 aNewB2DPolyPolygon.isClosed() ? SdrObjKind::Polygon : SdrObjKind::PolyLine,
2630 aNewB2DPolyPolygon));
2631 SfxItemSet aTempSet(*this);
2632 aTempSet.Put(makeSdrShadowItem(false));
2633 aTempSet.Put(XFillStyleItem(drawing::FillStyle_NONE));
2634 pStroke->SetMergedItemSet(aTempSet);
2635 rObjectList.push_back(std::pair< rtl::Reference<SdrPathObj>, double >(std::move(pStroke), dBrightness));
2638 else
2640 rtl::Reference<SdrPathObj> pObj;
2641 SfxItemSet aTempSet(*this);
2642 aTempSet.Put(makeSdrShadowItem(false));
2644 if(bNoFill)
2646 // see comment above about OBJ_PLIN
2647 pObj = new SdrPathObj(
2648 mrSdrObjCustomShape.getSdrModelFromSdrObject(),
2649 aNewB2DPolyPolygon.isClosed() ? SdrObjKind::Polygon : SdrObjKind::PolyLine,
2650 aNewB2DPolyPolygon);
2651 aTempSet.Put(XFillStyleItem(drawing::FillStyle_NONE));
2653 else
2655 aNewB2DPolyPolygon.setClosed(true);
2656 pObj = new SdrPathObj(
2657 mrSdrObjCustomShape.getSdrModelFromSdrObject(),
2658 SdrObjKind::Polygon,
2659 aNewB2DPolyPolygon);
2662 if(bNoStroke)
2664 aTempSet.Put(XLineStyleItem(drawing::LineStyle_NONE));
2667 pObj->SetMergedItemSet(aTempSet);
2668 rObjectList.push_back(std::pair< rtl::Reference<SdrPathObj>, double >(std::move(pObj), dBrightness));
2672 static void CorrectCalloutArrows(
2673 MSO_SPT eSpType,
2674 sal_uInt32 nLineObjectCount,
2675 std::vector< std::pair< rtl::Reference<SdrPathObj>, double> >& vObjectList )
2677 bool bAccent = false;
2678 switch( eSpType )
2680 case mso_sptCallout1 :
2681 case mso_sptBorderCallout1 :
2682 case mso_sptCallout90 :
2683 case mso_sptBorderCallout90 :
2684 default:
2685 break;
2687 case mso_sptAccentCallout1 :
2688 case mso_sptAccentBorderCallout1 :
2689 case mso_sptAccentCallout90 :
2690 case mso_sptAccentBorderCallout90 :
2692 sal_uInt32 nLine = 0;
2694 for ( const std::pair< rtl::Reference<SdrPathObj>, double >& rCandidate : vObjectList )
2696 SdrPathObj* pObj(rCandidate.first.get());
2698 if(pObj->IsLine())
2700 nLine++;
2701 if ( nLine == nLineObjectCount )
2703 pObj->ClearMergedItem( XATTR_LINESTART );
2704 pObj->ClearMergedItem( XATTR_LINEEND );
2709 break;
2711 // switch start & end
2712 case mso_sptAccentCallout2 :
2713 case mso_sptAccentBorderCallout2 :
2714 bAccent = true;
2715 [[fallthrough]];
2716 case mso_sptCallout2 :
2717 case mso_sptBorderCallout2 :
2719 sal_uInt32 nLine = 0;
2721 for ( const std::pair< rtl::Reference<SdrPathObj>, double >& rCandidate : vObjectList )
2723 SdrPathObj* pObj(rCandidate.first.get());
2725 if(pObj->IsLine())
2727 nLine++;
2728 if ( nLine == 1 )
2729 pObj->ClearMergedItem( XATTR_LINEEND );
2730 else if ( ( bAccent && ( nLine == nLineObjectCount - 1 ) ) || ( !bAccent && ( nLine == nLineObjectCount ) ) )
2731 pObj->ClearMergedItem( XATTR_LINESTART );
2732 else
2734 pObj->ClearMergedItem( XATTR_LINESTART );
2735 pObj->ClearMergedItem( XATTR_LINEEND );
2740 break;
2742 case mso_sptAccentCallout3 :
2743 case mso_sptAccentBorderCallout3 :
2744 case mso_sptCallout3 :
2745 case mso_sptBorderCallout3 :
2747 sal_uInt32 nLine = 0;
2749 for ( const std::pair< rtl::Reference<SdrPathObj>, double >& rCandidate : vObjectList )
2751 SdrPathObj* pObj(rCandidate.first.get());
2753 if(pObj->IsLine())
2755 if ( nLine )
2757 pObj->ClearMergedItem( XATTR_LINESTART );
2758 pObj->ClearMergedItem( XATTR_LINEEND );
2760 else
2761 EnhancedCustomShape2d::SwapStartAndEndArrow( pObj );
2763 nLine++;
2767 break;
2771 void EnhancedCustomShape2d::AdaptObjColor(
2772 SdrPathObj& rObj,
2773 double dBrightness,
2774 const SfxItemSet& rCustomShapeSet,
2775 sal_uInt32& nColorIndex,
2776 sal_uInt32 nColorCount)
2778 if ( rObj.IsLine() )
2779 return;
2781 const drawing::FillStyle eFillStyle = rObj.GetMergedItem(XATTR_FILLSTYLE).GetValue();
2782 if (eFillStyle == drawing::FillStyle_NONE)
2783 return;
2785 switch( eFillStyle )
2787 default:
2788 case drawing::FillStyle_SOLID:
2790 if ( nColorCount || 0.0 != dBrightness )
2792 Color aFillColor = GetColorData(
2793 rCustomShapeSet.Get( XATTR_FILLCOLOR ).GetColorValue(),
2794 std::min(nColorIndex, nColorCount-1),
2795 dBrightness );
2796 rObj.SetMergedItem( XFillColorItem( "", aFillColor ) );
2798 break;
2800 case drawing::FillStyle_GRADIENT:
2802 basegfx::BGradient aBGradient(rObj.GetMergedItem(XATTR_FILLGRADIENT).GetGradientValue());
2804 if ( nColorCount || 0.0 != dBrightness )
2806 basegfx::BColorStops aColorStops(aBGradient.GetColorStops());
2807 for (auto& candidate : aColorStops)
2809 candidate = basegfx::BColorStop(
2810 candidate.getStopOffset(),
2811 GetColorData(
2812 Color(candidate.getStopColor()),
2813 std::min(nColorIndex, nColorCount-1),
2814 dBrightness ).getBColor());
2816 aBGradient.SetColorStops(aColorStops);
2819 rObj.SetMergedItem( XFillGradientItem( "", aBGradient ) );
2820 break;
2822 case drawing::FillStyle_HATCH:
2824 XHatch aXHatch(rObj.GetMergedItem(XATTR_FILLHATCH).GetHatchValue());
2826 if ( nColorCount || 0.0 != dBrightness )
2828 aXHatch.SetColor(
2829 GetColorData(
2830 aXHatch.GetColor(),
2831 std::min(nColorIndex, nColorCount-1),
2832 dBrightness ));
2835 rObj.SetMergedItem( XFillHatchItem( "", aXHatch ) );
2836 break;
2838 case drawing::FillStyle_BITMAP:
2840 if ( nColorCount || 0.0 != dBrightness )
2842 BitmapEx aBitmap(rObj.GetMergedItem(XATTR_FILLBITMAP).GetGraphicObject().GetGraphic().GetBitmapEx());
2844 short nLuminancePercent = static_cast< short > ( GetLuminanceChange(
2845 std::min(nColorIndex, nColorCount-1)));
2846 aBitmap.Adjust( nLuminancePercent, 0, 0, 0, 0 );
2848 rObj.SetMergedItem(XFillBitmapItem(OUString(), Graphic(aBitmap)));
2851 break;
2855 if ( nColorIndex < nColorCount )
2856 nColorIndex++;
2859 rtl::Reference<SdrObject> EnhancedCustomShape2d::CreatePathObj( bool bLineGeometryNeededOnly )
2861 if ( !seqCoordinates.hasElements() )
2863 return nullptr;
2866 std::vector< std::pair< rtl::Reference<SdrPathObj>, double > > vObjectList;
2867 const bool bSortFilledObjectsToBack(SortFilledObjectsToBackByDefault(eSpType));
2868 sal_Int32 nSubPathIndex(0);
2869 sal_Int32 nSrcPt(0);
2870 sal_Int32 nSegmentInd(0);
2871 rtl::Reference<SdrObject> pRet;
2873 while( nSegmentInd <= seqSegments.getLength() )
2875 CreateSubPath(
2876 nSrcPt,
2877 nSegmentInd,
2878 vObjectList,
2879 bLineGeometryNeededOnly,
2880 bSortFilledObjectsToBack,
2881 nSubPathIndex);
2882 nSubPathIndex++;
2885 if ( !vObjectList.empty() )
2887 const SfxItemSet& rCustomShapeSet(mrSdrObjCustomShape.GetMergedItemSet());
2888 const sal_uInt32 nColorCount(nColorData >> 28);
2889 sal_uInt32 nColorIndex(0);
2891 // #i37011# remove invisible objects
2892 std::vector< std::pair< rtl::Reference<SdrPathObj>, double> > vNewList;
2894 for ( std::pair< rtl::Reference<SdrPathObj>, double >& rCandidate : vObjectList )
2896 SdrPathObj* pObj(rCandidate.first.get());
2897 const drawing::LineStyle eLineStyle(pObj->GetMergedItem(XATTR_LINESTYLE).GetValue());
2898 const drawing::FillStyle eFillStyle(pObj->GetMergedItem(XATTR_FILLSTYLE).GetValue());
2899 const auto pText = pObj->getActiveText();
2901 // #i40600# if bLineGeometryNeededOnly is set, linestyle does not matter
2902 if(pText || bLineGeometryNeededOnly || (drawing::LineStyle_NONE != eLineStyle) || (drawing::FillStyle_NONE != eFillStyle))
2903 vNewList.push_back(std::move(rCandidate));
2906 vObjectList = std::move(vNewList);
2908 if(1 == vObjectList.size())
2910 // a single object, correct some values
2911 AdaptObjColor(
2912 *vObjectList.begin()->first,
2913 vObjectList.begin()->second,
2914 rCustomShapeSet,
2915 nColorIndex,
2916 nColorCount);
2918 else
2920 sal_Int32 nLineObjectCount(0);
2922 // correct some values and collect content data
2923 for ( const std::pair< rtl::Reference<SdrPathObj>, double >& rCandidate : vObjectList )
2925 SdrPathObj* pObj(rCandidate.first.get());
2927 if(pObj->IsLine())
2929 nLineObjectCount++;
2931 else
2933 AdaptObjColor(
2934 *pObj,
2935 rCandidate.second,
2936 rCustomShapeSet,
2937 nColorIndex,
2938 nColorCount);
2940 // OperationSmiley: when we have access to the SdrObjCustomShape and the
2941 // CustomShape is built with more than a single filled Geometry, use it
2942 // to define that all helper geometries defined here (SdrObjects currently)
2943 // will use the same FillGeometryDefinition (from the referenced SdrObjCustomShape).
2944 // This will all same-filled objects look like filled smoothly with the same style.
2945 pObj->setFillGeometryDefiningShape(&mrSdrObjCustomShape);
2949 // #i88870# correct line arrows for callouts
2950 if ( nLineObjectCount )
2952 CorrectCalloutArrows(
2953 eSpType,
2954 nLineObjectCount,
2955 vObjectList);
2958 // sort objects so that filled ones are in front. Necessary
2959 // for some strange objects
2960 if(bSortFilledObjectsToBack)
2962 std::vector< std::pair< rtl::Reference<SdrPathObj>, double> > vTempList;
2963 vTempList.reserve(vObjectList.size());
2965 for ( std::pair< rtl::Reference<SdrPathObj>, double >& rCandidate : vObjectList )
2967 SdrPathObj* pObj(rCandidate.first.get());
2968 if ( !pObj->IsLine() )
2969 vTempList.push_back(std::move(rCandidate));
2972 for ( std::pair< rtl::Reference<SdrPathObj>, double >& rCandidate : vObjectList )
2974 if ( rCandidate.first )
2975 vTempList.push_back(std::move(rCandidate));
2978 vObjectList = std::move(vTempList);
2983 // #i37011#
2984 if(!vObjectList.empty())
2986 // copy remaining objects to pRet
2987 if(vObjectList.size() > 1)
2989 pRet = new SdrObjGroup(mrSdrObjCustomShape.getSdrModelFromSdrObject());
2991 for ( std::pair< rtl::Reference<SdrPathObj>, double >& rCandidate : vObjectList )
2993 pRet->GetSubList()->NbcInsertObject(rCandidate.first.get());
2996 else if(1 == vObjectList.size())
2998 pRet = vObjectList.begin()->first;
3001 if(pRet)
3003 // move to target position
3004 tools::Rectangle aCurRect(pRet->GetSnapRect());
3005 aCurRect.Move(aLogicRect.Left(), aLogicRect.Top());
3006 pRet->NbcSetSnapRect(aCurRect);
3010 return pRet;
3013 rtl::Reference<SdrObject> EnhancedCustomShape2d::CreateObject( bool bLineGeometryNeededOnly )
3015 rtl::Reference<SdrObject> pRet;
3017 if ( eSpType == mso_sptRectangle )
3019 pRet = new SdrRectObj(mrSdrObjCustomShape.getSdrModelFromSdrObject(), aLogicRect);
3020 pRet->SetMergedItemSet( *this );
3022 if ( !pRet )
3023 pRet = CreatePathObj( bLineGeometryNeededOnly );
3025 return pRet;
3028 void EnhancedCustomShape2d::ApplyGluePoints( SdrObject* pObj )
3030 if ( !pObj )
3031 return;
3033 for ( const auto& rGluePoint : std::as_const(seqGluePoints) )
3035 SdrGluePoint aGluePoint;
3037 aGluePoint.SetPos( GetPoint( rGluePoint, !bOOXMLShape, true ) );
3038 aGluePoint.SetPercent( false );
3039 aGluePoint.SetAlign( SdrAlign::VERT_TOP | SdrAlign::HORZ_LEFT );
3040 aGluePoint.SetEscDir( SdrEscapeDirection::SMART );
3041 SdrGluePointList* pList = pObj->ForceGluePointList();
3042 if( pList )
3043 /* sal_uInt16 nId = */ pList->Insert( aGluePoint );
3047 rtl::Reference<SdrObject> EnhancedCustomShape2d::CreateLineGeometry()
3049 return CreateObject( true );
3053 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */