tdf#130857 qt weld: Support mail merge "Server Auth" dialog
[LibreOffice.git] / svx / source / svdraw / svdoashp.cxx
blob996071b27e92fd938160020283d29caa46db07ae
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 <vcl/bitmap/BitmapShadowFilter.hxx>
21 #include <svx/svdoashp.hxx>
22 #include <svx/unoapi.hxx>
23 #include <com/sun/star/loader/CannotActivateFactoryException.hpp>
24 #include <com/sun/star/drawing/XShape.hpp>
25 #include <com/sun/star/drawing/XCustomShapeEngine.hpp>
26 #include <com/sun/star/drawing/PolyPolygonBezierCoords.hpp>
27 #include <com/sun/star/beans/PropertyValue.hpp>
28 #include <com/sun/star/awt/Rectangle.hpp>
29 #include <com/sun/star/uno/XComponentContext.hpp>
30 #include <comphelper/processfactory.hxx>
31 #include <comphelper/propertyvalue.hxx>
32 #include <comphelper/sequenceashashmap.hxx>
33 #include <com/sun/star/uno/Sequence.h>
34 #include <tools/helpers.hxx>
35 #include <svx/svddrag.hxx>
36 #include <svx/svddrgmt.hxx>
37 #include <svx/svdmodel.hxx>
38 #include <svx/svdpage.hxx>
39 #include <svx/svditer.hxx>
40 #include <svx/svdobj.hxx>
41 #include <svx/svdtrans.hxx>
42 #include <svx/dialmgr.hxx>
43 #include <svx/strings.hrc>
44 #include <editeng/eeitem.hxx>
45 #include <editeng/editstat.hxx>
46 #include <editeng/adjustitem.hxx>
47 #include <svx/svdoutl.hxx>
48 #include <editeng/outlobj.hxx>
49 #include <svx/sdtfchim.hxx>
50 #include <svx/EnhancedCustomShapeGeometry.hxx>
51 #include <svx/EnhancedCustomShapeTypeNames.hxx>
52 #include <svx/EnhancedCustomShape2d.hxx>
53 #include <com/sun/star/beans/PropertyValues.hpp>
54 #include <com/sun/star/drawing/EnhancedCustomShapeAdjustmentValue.hpp>
55 #include <com/sun/star/drawing/EnhancedCustomShapeParameterPair.hpp>
56 #include <com/sun/star/drawing/EnhancedCustomShapeTextFrame.hpp>
57 #include <com/sun/star/drawing/EnhancedCustomShapeSegment.hpp>
58 #include <com/sun/star/drawing/EnhancedCustomShapeSegmentCommand.hpp>
59 #include <editeng/writingmodeitem.hxx>
60 #include <svx/xlineit0.hxx>
61 #include <svx/xlnclit.hxx>
62 #include <sdr/properties/customshapeproperties.hxx>
63 #include <sdr/contact/viewcontactofsdrobjcustomshape.hxx>
64 #include <svx/xlntrit.hxx>
65 #include <svx/xfillit0.hxx>
66 #include <svx/xfltrit.hxx>
67 #include <svx/xflclit.hxx>
68 #include <svx/xflgrit.hxx>
69 #include <svx/xflhtit.hxx>
70 #include <svx/xbtmpit.hxx>
71 #include <vcl/virdev.hxx>
72 #include <svx/svdview.hxx>
73 #include <svx/sdmetitm.hxx>
74 #include <svx/sdprcitm.hxx>
75 #include <svx/sdshitm.hxx>
76 #include <svx/sdsxyitm.hxx>
77 #include <svx/sdtmfitm.hxx>
78 #include <svx/sdasitm.hxx>
79 #include <basegfx/polygon/b2dpolypolygontools.hxx>
80 #include <basegfx/matrix/b2dhommatrix.hxx>
81 #include <basegfx/matrix/b2dhommatrixtools.hxx>
82 #include <basegfx/polygon/b2dpolygon.hxx>
83 #include <basegfx/polygon/b2dpolygontools.hxx>
84 #include <basegfx/range/b2drange.hxx>
85 #include <svdobjplusdata.hxx>
86 #include <sal/log.hxx>
87 #include <o3tl/string_view.hxx>
88 #include "presetooxhandleadjustmentrelations.hxx"
89 #include <editeng/frmdiritem.hxx>
91 using namespace ::com::sun::star;
93 static void lcl_ShapeSegmentFromBinary( drawing::EnhancedCustomShapeSegment& rSegInfo, sal_uInt16 nSDat )
95 switch( nSDat >> 8 )
97 case 0x00 :
98 rSegInfo.Command = drawing::EnhancedCustomShapeSegmentCommand::LINETO;
99 rSegInfo.Count = nSDat & 0xff;
100 if ( !rSegInfo.Count )
101 rSegInfo.Count = 1;
102 break;
103 case 0x20 :
104 rSegInfo.Command = drawing::EnhancedCustomShapeSegmentCommand::CURVETO;
105 rSegInfo.Count = nSDat & 0xff;
106 if ( !rSegInfo.Count )
107 rSegInfo.Count = 1;
108 break;
109 case 0x40 :
110 rSegInfo.Command = drawing::EnhancedCustomShapeSegmentCommand::MOVETO;
111 rSegInfo.Count = nSDat & 0xff;
112 if ( !rSegInfo.Count )
113 rSegInfo.Count = 1;
114 break;
115 case 0x60 :
116 rSegInfo.Command = drawing::EnhancedCustomShapeSegmentCommand::CLOSESUBPATH;
117 rSegInfo.Count = 0;
118 break;
119 case 0x80 :
120 rSegInfo.Command = drawing::EnhancedCustomShapeSegmentCommand::ENDSUBPATH;
121 rSegInfo.Count = 0;
122 break;
123 case 0xa1 :
124 rSegInfo.Command = drawing::EnhancedCustomShapeSegmentCommand::ANGLEELLIPSETO;
125 rSegInfo.Count = ( nSDat & 0xff ) / 3;
126 break;
127 case 0xa2 :
128 rSegInfo.Command = drawing::EnhancedCustomShapeSegmentCommand::ANGLEELLIPSE;
129 rSegInfo.Count = ( nSDat & 0xff ) / 3;
130 break;
131 case 0xa3 :
132 rSegInfo.Command = drawing::EnhancedCustomShapeSegmentCommand::ARCTO;
133 rSegInfo.Count = ( nSDat & 0xff ) >> 2;
134 break;
135 case 0xa4 :
136 rSegInfo.Command = drawing::EnhancedCustomShapeSegmentCommand::ARC;
137 rSegInfo.Count = ( nSDat & 0xff ) >> 2;
138 break;
139 case 0xa5 :
140 rSegInfo.Command = drawing::EnhancedCustomShapeSegmentCommand::CLOCKWISEARCTO;
141 rSegInfo.Count = ( nSDat & 0xff ) >> 2;
142 break;
143 case 0xa6 :
144 rSegInfo.Command = drawing::EnhancedCustomShapeSegmentCommand::CLOCKWISEARC;
145 rSegInfo.Count = ( nSDat & 0xff ) >> 2;
146 break;
147 case 0xa7 :
148 rSegInfo.Command = drawing::EnhancedCustomShapeSegmentCommand::ELLIPTICALQUADRANTX;
149 rSegInfo.Count = nSDat & 0xff;
150 break;
151 case 0xa8 :
152 rSegInfo.Command = drawing::EnhancedCustomShapeSegmentCommand::ELLIPTICALQUADRANTY;
153 rSegInfo.Count = nSDat & 0xff;
154 break;
155 case 0xaa :
156 rSegInfo.Command = drawing::EnhancedCustomShapeSegmentCommand::NOFILL;
157 rSegInfo.Count = 0;
158 break;
159 case 0xab :
160 rSegInfo.Command = drawing::EnhancedCustomShapeSegmentCommand::NOSTROKE;
161 rSegInfo.Count = 0;
162 break;
163 default:
164 case 0xf8 :
165 rSegInfo.Command = drawing::EnhancedCustomShapeSegmentCommand::UNKNOWN;
166 rSegInfo.Count = nSDat;
167 break;
171 static MSO_SPT ImpGetCustomShapeType( const SdrObjCustomShape& rCustoShape )
173 MSO_SPT eRetValue = mso_sptNil;
175 OUString aEngine( rCustoShape.GetMergedItem( SDRATTR_CUSTOMSHAPE_ENGINE ).GetValue() );
176 if ( aEngine.isEmpty() || aEngine == "com.sun.star.drawing.EnhancedCustomShapeEngine" )
178 OUString sShapeType;
179 const SdrCustomShapeGeometryItem& rGeometryItem( rCustoShape.GetMergedItem( SDRATTR_CUSTOMSHAPE_GEOMETRY ) );
180 const uno::Any* pAny = rGeometryItem.GetPropertyValueByName( u"Type"_ustr );
181 if ( pAny && ( *pAny >>= sShapeType ) )
182 eRetValue = EnhancedCustomShapeTypeNames::Get( sShapeType );
184 return eRetValue;
187 static bool ImpVerticalSwitch( const SdrObjCustomShape& rCustoShape )
189 bool bRet = false;
190 MSO_SPT eShapeType( ImpGetCustomShapeType( rCustoShape ) );
191 switch( eShapeType )
193 case mso_sptAccentBorderCallout90 : // 2 ortho
194 case mso_sptBorderCallout1 : // 2 diag
195 case mso_sptBorderCallout2 : // 3
197 bRet = true;
199 break;
200 default: break;
202 return bRet;
205 // #i37011# create a clone with all attributes changed to shadow attributes
206 // and translation executed, too.
207 static rtl::Reference<SdrObject> ImpCreateShadowObjectClone(const SdrObject& rOriginal, const SfxItemSet& rOriginalSet)
209 rtl::Reference<SdrObject> pRetval;
210 const bool bShadow(rOriginalSet.Get(SDRATTR_SHADOW).GetValue());
212 if(bShadow)
214 // create a shadow representing object
215 const sal_Int32 nXDist(rOriginalSet.Get(SDRATTR_SHADOWXDIST).GetValue());
216 const sal_Int32 nYDist(rOriginalSet.Get(SDRATTR_SHADOWYDIST).GetValue());
217 const ::Color aShadowColor(rOriginalSet.Get(SDRATTR_SHADOWCOLOR).GetColorValue());
218 const sal_uInt16 nShadowTransparence(rOriginalSet.Get(SDRATTR_SHADOWTRANSPARENCE).GetValue());
219 pRetval = rOriginal.CloneSdrObject(rOriginal.getSdrModelFromSdrObject());
220 DBG_ASSERT(pRetval, "ImpCreateShadowObjectClone: Could not clone object (!)");
222 // look for used stuff
223 SdrObjListIter aIterator(rOriginal);
224 bool bLineUsed(false);
225 bool bAllFillUsed(false);
226 bool bSolidFillUsed(false);
227 bool bGradientFillUsed(false);
228 bool bHatchFillUsed(false);
229 bool bBitmapFillUsed(false);
231 while(aIterator.IsMore())
233 SdrObject* pObj = aIterator.Next();
234 drawing::FillStyle eFillStyle = pObj->GetMergedItem(XATTR_FILLSTYLE).GetValue();
236 if(!bLineUsed)
238 drawing::LineStyle eLineStyle = pObj->GetMergedItem(XATTR_LINESTYLE).GetValue();
240 if(drawing::LineStyle_NONE != eLineStyle)
242 bLineUsed = true;
246 if(!bAllFillUsed)
248 if(!bSolidFillUsed && drawing::FillStyle_SOLID == eFillStyle)
250 bSolidFillUsed = true;
251 bAllFillUsed = (bSolidFillUsed && bGradientFillUsed && bHatchFillUsed && bBitmapFillUsed);
253 if(!bGradientFillUsed && drawing::FillStyle_GRADIENT == eFillStyle)
255 bGradientFillUsed = true;
256 bAllFillUsed = (bSolidFillUsed && bGradientFillUsed && bHatchFillUsed && bBitmapFillUsed);
258 if(!bHatchFillUsed && drawing::FillStyle_HATCH == eFillStyle)
260 bHatchFillUsed = true;
261 bAllFillUsed = (bSolidFillUsed && bGradientFillUsed && bHatchFillUsed && bBitmapFillUsed);
263 if(!bBitmapFillUsed && drawing::FillStyle_BITMAP == eFillStyle)
265 bBitmapFillUsed = true;
266 bAllFillUsed = (bSolidFillUsed && bGradientFillUsed && bHatchFillUsed && bBitmapFillUsed);
271 // translate to shadow coordinates
272 pRetval->NbcMove(Size(nXDist, nYDist));
274 // set items as needed
275 SfxItemSet aTempSet(rOriginalSet);
277 // if a SvxWritingModeItem (Top->Bottom) is set the text object
278 // is creating a paraobject, but paraobjects can not be created without model. So
279 // we are preventing the crash by setting the writing mode always left to right,
280 // this is not bad since our shadow geometry does not contain text.
281 aTempSet.Put(SvxWritingModeItem(text::WritingMode_LR_TB, SDRATTR_TEXTDIRECTION));
283 // no shadow
284 aTempSet.Put(makeSdrShadowItem(false));
285 aTempSet.Put(makeSdrShadowXDistItem(0));
286 aTempSet.Put(makeSdrShadowYDistItem(0));
288 // line color and transparency like shadow
289 if(bLineUsed)
291 aTempSet.Put(XLineColorItem(OUString(), aShadowColor));
292 aTempSet.Put(XLineTransparenceItem(nShadowTransparence));
295 // fill color and transparency like shadow
296 if(bSolidFillUsed)
298 aTempSet.Put(XFillColorItem(OUString(), aShadowColor));
299 aTempSet.Put(XFillTransparenceItem(nShadowTransparence));
302 // gradient and transparency like shadow
303 if(bGradientFillUsed)
305 basegfx::BGradient aGradient(rOriginalSet.Get(XATTR_FILLGRADIENT).GetGradientValue());
306 sal_uInt8 nStartLuminance(Color(aGradient.GetColorStops().front().getStopColor()).GetLuminance());
307 sal_uInt8 nEndLuminance(Color(aGradient.GetColorStops().back().getStopColor()).GetLuminance());
309 if(aGradient.GetStartIntens() != 100)
311 nStartLuminance = static_cast<sal_uInt8>(nStartLuminance * (static_cast<double>(aGradient.GetStartIntens()) / 100.0));
314 if(aGradient.GetEndIntens() != 100)
316 nEndLuminance = static_cast<sal_uInt8>(nEndLuminance * (static_cast<double>(aGradient.GetEndIntens()) / 100.0));
319 ::Color aStartColor(
320 static_cast<sal_uInt8>((nStartLuminance * aShadowColor.GetRed()) / 256),
321 static_cast<sal_uInt8>((nStartLuminance * aShadowColor.GetGreen()) / 256),
322 static_cast<sal_uInt8>((nStartLuminance * aShadowColor.GetBlue()) / 256));
324 ::Color aEndColor(
325 static_cast<sal_uInt8>((nEndLuminance * aShadowColor.GetRed()) / 256),
326 static_cast<sal_uInt8>((nEndLuminance * aShadowColor.GetGreen()) / 256),
327 static_cast<sal_uInt8>((nEndLuminance * aShadowColor.GetBlue()) / 256));
329 aGradient.SetColorStops(
330 basegfx::BColorStops(
331 aStartColor.getBColor(),
332 aEndColor.getBColor()));
333 aTempSet.Put(XFillGradientItem(aGradient));
334 aTempSet.Put(XFillTransparenceItem(nShadowTransparence));
337 // hatch and transparency like shadow
338 if(bHatchFillUsed)
340 XHatch aHatch(rOriginalSet.Get(XATTR_FILLHATCH).GetHatchValue());
341 aHatch.SetColor(aShadowColor);
342 aTempSet.Put(XFillHatchItem(aHatch));
343 aTempSet.Put(XFillTransparenceItem(nShadowTransparence));
346 // bitmap and transparency like shadow
347 if(bBitmapFillUsed)
349 GraphicObject aGraphicObject(rOriginalSet.Get(XATTR_FILLBITMAP).GetGraphicObject());
350 BitmapEx aBitmapEx(aGraphicObject.GetGraphic().GetBitmapEx());
352 if(!aBitmapEx.IsEmpty())
354 ScopedVclPtr<VirtualDevice> pVirDev(VclPtr<VirtualDevice>::Create());
355 pVirDev->SetOutputSizePixel(aBitmapEx.GetSizePixel());
356 BitmapFilter::Filter(aBitmapEx, BitmapShadowFilter(aShadowColor));
357 pVirDev->DrawBitmapEx(Point(), aBitmapEx);
358 aGraphicObject.SetGraphic(Graphic(pVirDev->GetBitmapEx(Point(0,0), aBitmapEx.GetSizePixel())));
361 aTempSet.Put(XFillBitmapItem(aGraphicObject));
362 aTempSet.Put(XFillTransparenceItem(nShadowTransparence));
365 // set attributes and paint shadow object
366 pRetval->SetMergedItemSet( aTempSet );
368 return pRetval;
372 uno::Reference<drawing::XCustomShapeEngine> const & SdrObjCustomShape::GetCustomShapeEngine() const
374 if (mxCustomShapeEngine.is())
375 return mxCustomShapeEngine;
377 uno::Reference<drawing::XShape> aXShape = GetXShapeForSdrObject(const_cast<SdrObjCustomShape*>(this));
378 if ( !aXShape )
379 return mxCustomShapeEngine;
381 const uno::Reference<uno::XComponentContext>& xContext( ::comphelper::getProcessComponentContext() );
383 OUString aEngine(GetMergedItem( SDRATTR_CUSTOMSHAPE_ENGINE ).GetValue());
384 static constexpr OUStringLiteral sEnhancedCustomShapeEngine = u"com.sun.star.drawing.EnhancedCustomShapeEngine";
385 if ( aEngine.isEmpty() )
386 aEngine = sEnhancedCustomShapeEngine;
389 static constexpr OUString sCustomShape = u"CustomShape"_ustr;
390 uno::Sequence<beans::PropertyValue> aPropValues{ comphelper::makePropertyValue(sCustomShape,
391 aXShape) };
392 uno::Sequence<uno::Any> aArgument{ uno::Any(aPropValues) };
395 uno::Reference<uno::XInterface> xInterface(xContext->getServiceManager()->createInstanceWithArgumentsAndContext(aEngine, aArgument, xContext));
396 if (xInterface.is())
397 mxCustomShapeEngine.set(xInterface, uno::UNO_QUERY);
399 catch (const loader::CannotActivateFactoryException&)
404 return mxCustomShapeEngine;
407 const SdrObject* SdrObjCustomShape::GetSdrObjectFromCustomShape() const
409 if ( !mXRenderedCustomShape.is() )
411 uno::Reference<drawing::XCustomShapeEngine> xCustomShapeEngine( GetCustomShapeEngine() );
412 if ( xCustomShapeEngine.is() )
413 const_cast<SdrObjCustomShape*>(this)->mXRenderedCustomShape = xCustomShapeEngine->render();
415 SdrObject* pRenderedCustomShape = mXRenderedCustomShape.is()
416 ? SdrObject::getSdrObjectFromXShape( mXRenderedCustomShape )
417 : nullptr;
418 return pRenderedCustomShape;
421 // #i37011# Shadow geometry creation
422 const SdrObject* SdrObjCustomShape::GetSdrObjectShadowFromCustomShape() const
424 if(!mpLastShadowGeometry)
426 const SdrObject* pSdrObject = GetSdrObjectFromCustomShape();
427 if(pSdrObject)
429 const SfxItemSet& rOriginalSet = GetObjectItemSet();
430 const bool bShadow(rOriginalSet.Get( SDRATTR_SHADOW ).GetValue());
432 if(bShadow)
434 // create a clone with all attributes changed to shadow attributes
435 // and translation executed, too.
436 const_cast<SdrObjCustomShape*>(this)->mpLastShadowGeometry =
437 ImpCreateShadowObjectClone(*pSdrObject, rOriginalSet);
442 return mpLastShadowGeometry.get();
445 bool SdrObjCustomShape::IsTextPath() const
447 static constexpr OUString sTextPath( u"TextPath"_ustr );
448 bool bTextPathOn = false;
449 const SdrCustomShapeGeometryItem& rGeometryItem = GetMergedItem( SDRATTR_CUSTOMSHAPE_GEOMETRY );
450 const uno::Any* pAny = rGeometryItem.GetPropertyValueByName( sTextPath, sTextPath );
451 if ( pAny )
452 *pAny >>= bTextPathOn;
453 return bTextPathOn;
456 bool SdrObjCustomShape::UseNoFillStyle() const
458 bool bRet = false;
459 OUString sShapeType;
460 static constexpr OUString sType( u"Type"_ustr );
461 const SdrCustomShapeGeometryItem& rGeometryItem( GetMergedItem( SDRATTR_CUSTOMSHAPE_GEOMETRY ) );
462 const uno::Any* pAny = rGeometryItem.GetPropertyValueByName( sType );
463 if ( pAny )
464 *pAny >>= sShapeType;
465 bRet = !IsCustomShapeFilledByDefault( EnhancedCustomShapeTypeNames::Get( sType ) );
467 return bRet;
470 bool SdrObjCustomShape::IsMirroredX() const
472 bool bMirroredX = false;
473 const SdrCustomShapeGeometryItem & rGeometryItem( GetMergedItem( SDRATTR_CUSTOMSHAPE_GEOMETRY ) );
474 const uno::Any* pAny = rGeometryItem.GetPropertyValueByName( u"MirroredX"_ustr );
475 if ( pAny )
476 *pAny >>= bMirroredX;
477 return bMirroredX;
479 bool SdrObjCustomShape::IsMirroredY() const
481 bool bMirroredY = false;
482 const SdrCustomShapeGeometryItem & rGeometryItem( GetMergedItem( SDRATTR_CUSTOMSHAPE_GEOMETRY ) );
483 const uno::Any* pAny = rGeometryItem.GetPropertyValueByName( u"MirroredY"_ustr );
484 if ( pAny )
485 *pAny >>= bMirroredY;
486 return bMirroredY;
488 void SdrObjCustomShape::SetMirroredX( const bool bMirrorX )
490 SdrCustomShapeGeometryItem aGeometryItem( GetMergedItem( SDRATTR_CUSTOMSHAPE_GEOMETRY ) );
491 beans::PropertyValue aPropVal;
492 aPropVal.Name = "MirroredX";
493 aPropVal.Value <<= bMirrorX;
494 aGeometryItem.SetPropertyValue( aPropVal );
495 SetMergedItem( aGeometryItem );
497 void SdrObjCustomShape::SetMirroredY( const bool bMirrorY )
499 SdrCustomShapeGeometryItem aGeometryItem( GetMergedItem( SDRATTR_CUSTOMSHAPE_GEOMETRY ) );
500 beans::PropertyValue aPropVal;
501 aPropVal.Name = "MirroredY";
502 aPropVal.Value <<= bMirrorY;
503 aGeometryItem.SetPropertyValue( aPropVal );
504 SetMergedItem( aGeometryItem );
507 double SdrObjCustomShape::GetExtraTextRotation( const bool bPreRotation ) const
509 double fExtraTextRotateAngle = 0.0;
510 if (bPreRotation)
512 // textPreRotateAngle might be set by macro or diagram (SmartArt) import
513 const uno::Any* pAny;
514 const SdrCustomShapeGeometryItem& rGeometryItem = GetMergedItem( SDRATTR_CUSTOMSHAPE_GEOMETRY );
515 pAny = rGeometryItem.GetPropertyValueByName(u"TextPreRotateAngle"_ustr);
516 if ( pAny )
517 *pAny >>= fExtraTextRotateAngle;
519 // As long as the edit engine is not able to render these text directions we
520 // emulate them by setting a suitable text pre-rotation.
521 const SvxFrameDirectionItem& rDirectionItem = GetMergedItem(SDRATTR_WRITINGMODE2);
522 if (rDirectionItem.GetValue() == SvxFrameDirection::Vertical_RL_TB90)
523 fExtraTextRotateAngle -= 90;
524 else if (rDirectionItem.GetValue() == SvxFrameDirection::Vertical_LR_BT)
525 fExtraTextRotateAngle -=270;
527 else
529 const uno::Any* pAny;
530 const SdrCustomShapeGeometryItem& rGeometryItem = GetMergedItem( SDRATTR_CUSTOMSHAPE_GEOMETRY );
531 pAny = rGeometryItem.GetPropertyValueByName(u"TextRotateAngle"_ustr);
532 if ( pAny )
533 *pAny >>= fExtraTextRotateAngle;
535 return fExtraTextRotateAngle;
538 bool SdrObjCustomShape::GetTextBounds( tools::Rectangle& rTextBound ) const
540 bool bRet = false;
542 uno::Reference<drawing::XCustomShapeEngine> xCustomShapeEngine( GetCustomShapeEngine() );
543 if ( xCustomShapeEngine.is() )
545 awt::Rectangle aR( xCustomShapeEngine->getTextBounds() );
546 if ( aR.Width > 1 && aR.Height > 1 )
548 rTextBound = tools::Rectangle( Point( aR.X, aR.Y ), Size( aR.Width, aR.Height ) );
549 bRet = true;
552 return bRet;
554 basegfx::B2DPolyPolygon SdrObjCustomShape::GetLineGeometry( const bool bBezierAllowed ) const
556 basegfx::B2DPolyPolygon aRetval;
557 uno::Reference<drawing::XCustomShapeEngine> xCustomShapeEngine( GetCustomShapeEngine() );
558 if ( xCustomShapeEngine.is() )
560 drawing::PolyPolygonBezierCoords aBezierCoords = xCustomShapeEngine->getLineGeometry();
563 aRetval = basegfx::utils::UnoPolyPolygonBezierCoordsToB2DPolyPolygon( aBezierCoords );
564 if ( !bBezierAllowed && aRetval.areControlPointsUsed())
566 aRetval = basegfx::utils::adaptiveSubdivideByAngle(aRetval);
569 catch ( const lang::IllegalArgumentException & )
573 return aRetval;
576 std::vector< SdrCustomShapeInteraction > SdrObjCustomShape::GetInteractionHandles() const
578 std::vector< SdrCustomShapeInteraction > aRet;
581 uno::Reference<drawing::XCustomShapeEngine> xCustomShapeEngine( GetCustomShapeEngine() );
582 if ( xCustomShapeEngine.is() )
584 int i;
585 uno::Sequence<uno::Reference<drawing::XCustomShapeHandle>> xInteractionHandles( xCustomShapeEngine->getInteraction() );
586 for ( i = 0; i < xInteractionHandles.getLength(); i++ )
588 if ( xInteractionHandles[ i ].is() )
590 SdrCustomShapeInteraction aSdrCustomShapeInteraction;
591 aSdrCustomShapeInteraction.xInteraction = xInteractionHandles[ i ];
592 aSdrCustomShapeInteraction.aPosition = xInteractionHandles[ i ]->getPosition();
594 CustomShapeHandleModes nMode = CustomShapeHandleModes::NONE;
595 switch( ImpGetCustomShapeType( *this ) )
597 case mso_sptAccentBorderCallout90 : // 2 ortho
599 if (i == 0)
600 nMode |= CustomShapeHandleModes::RESIZE_FIXED | CustomShapeHandleModes::CREATE_FIXED;
601 else if (i == 1)
602 nMode |= CustomShapeHandleModes::RESIZE_ABSOLUTE_X | CustomShapeHandleModes::RESIZE_ABSOLUTE_Y | CustomShapeHandleModes::MOVE_SHAPE | CustomShapeHandleModes::ORTHO4;
604 break;
606 case mso_sptChevron :
607 case mso_sptHomePlate :
608 nMode |= CustomShapeHandleModes::RESIZE_ABSOLUTE_NEGX;
609 break;
611 case mso_sptWedgeRectCallout :
612 case mso_sptWedgeRRectCallout :
613 case mso_sptCloudCallout :
614 case mso_sptWedgeEllipseCallout :
616 if (i == 0)
617 nMode |= CustomShapeHandleModes::RESIZE_FIXED;
619 break;
621 case mso_sptBorderCallout1 : // 2 diag
623 if (i == 0)
624 nMode |= CustomShapeHandleModes::RESIZE_FIXED | CustomShapeHandleModes::CREATE_FIXED;
625 else if (i == 1)
626 nMode |= CustomShapeHandleModes::RESIZE_ABSOLUTE_X | CustomShapeHandleModes::RESIZE_ABSOLUTE_Y | CustomShapeHandleModes::MOVE_SHAPE;
628 break;
629 case mso_sptBorderCallout2 : // 3
631 if (i == 0)
632 nMode |= CustomShapeHandleModes::RESIZE_FIXED | CustomShapeHandleModes::CREATE_FIXED;
633 else if (i == 2)
634 nMode |= CustomShapeHandleModes::RESIZE_ABSOLUTE_X | CustomShapeHandleModes::RESIZE_ABSOLUTE_Y | CustomShapeHandleModes::MOVE_SHAPE;
636 break;
637 case mso_sptCallout90 :
638 case mso_sptAccentCallout90 :
639 case mso_sptBorderCallout90 :
640 case mso_sptCallout1 :
641 case mso_sptCallout2 :
642 case mso_sptCallout3 :
643 case mso_sptAccentCallout1 :
644 case mso_sptAccentCallout2 :
645 case mso_sptAccentCallout3 :
646 case mso_sptBorderCallout3 :
647 case mso_sptAccentBorderCallout1 :
648 case mso_sptAccentBorderCallout2 :
649 case mso_sptAccentBorderCallout3 :
651 if (i == 0)
652 nMode |= CustomShapeHandleModes::RESIZE_FIXED | CustomShapeHandleModes::CREATE_FIXED;
654 break;
655 default: break;
657 aSdrCustomShapeInteraction.nMode = nMode;
658 aRet.push_back( aSdrCustomShapeInteraction );
663 catch( const uno::RuntimeException& )
666 return aRet;
670 // BaseProperties section
671 #define DEFAULT_MINIMUM_SIGNED_COMPARE (sal_Int32(0x80000000))
672 #define DEFAULT_MAXIMUM_SIGNED_COMPARE (sal_Int32(0x7fffffff))
674 static sal_Int32 GetNumberOfProperties ( const SvxMSDffHandle* pData )
676 sal_Int32 nPropertiesNeeded=1; // position is always needed
677 SvxMSDffHandleFlags nFlags = pData->nFlags;
679 if ( nFlags & SvxMSDffHandleFlags::MIRRORED_X )
680 nPropertiesNeeded++;
681 if ( nFlags & SvxMSDffHandleFlags::MIRRORED_Y )
682 nPropertiesNeeded++;
683 if ( nFlags & SvxMSDffHandleFlags::SWITCHED )
684 nPropertiesNeeded++;
685 if ( nFlags & SvxMSDffHandleFlags::POLAR )
687 nPropertiesNeeded++;
688 if ( nFlags & SvxMSDffHandleFlags::RADIUS_RANGE )
690 if ( pData->nRangeXMin != DEFAULT_MINIMUM_SIGNED_COMPARE )
691 nPropertiesNeeded++;
692 if ( pData->nRangeXMax != DEFAULT_MAXIMUM_SIGNED_COMPARE )
693 nPropertiesNeeded++;
696 else if ( nFlags & SvxMSDffHandleFlags::RANGE )
698 if ( pData->nRangeXMin != DEFAULT_MINIMUM_SIGNED_COMPARE )
699 nPropertiesNeeded++;
700 if ( pData->nRangeXMax != DEFAULT_MAXIMUM_SIGNED_COMPARE )
701 nPropertiesNeeded++;
702 if ( pData->nRangeYMin != DEFAULT_MINIMUM_SIGNED_COMPARE )
703 nPropertiesNeeded++;
704 if ( pData->nRangeYMax != DEFAULT_MAXIMUM_SIGNED_COMPARE )
705 nPropertiesNeeded++;
708 return nPropertiesNeeded;
711 static void lcl_ShapePropertiesFromDFF( const SvxMSDffHandle* pData, beans::PropertyValues& rPropValues )
713 SvxMSDffHandleFlags nFlags = pData->nFlags;
714 sal_Int32 n=0;
715 auto pPropValues = rPropValues.getArray();
717 // POSITION
719 drawing::EnhancedCustomShapeParameterPair aPosition;
720 EnhancedCustomShape2d::SetEnhancedCustomShapeHandleParameter( aPosition.First, pData->nPositionX, true, true );
721 EnhancedCustomShape2d::SetEnhancedCustomShapeHandleParameter( aPosition.Second, pData->nPositionY, true, false );
722 pPropValues[ n ].Name = "Position";
723 pPropValues[ n++ ].Value <<= aPosition;
725 if ( nFlags & SvxMSDffHandleFlags::MIRRORED_X )
727 pPropValues[ n ].Name = "MirroredX";
728 pPropValues[ n++ ].Value <<= true;
730 if ( nFlags & SvxMSDffHandleFlags::MIRRORED_Y )
732 pPropValues[ n ].Name = "MirroredY";
733 pPropValues[ n++ ].Value <<= true;
735 if ( nFlags & SvxMSDffHandleFlags::SWITCHED )
737 pPropValues[ n ].Name = "Switched";
738 pPropValues[ n++ ].Value <<= true;
740 if ( nFlags & SvxMSDffHandleFlags::POLAR )
742 drawing::EnhancedCustomShapeParameterPair aCenter;
743 EnhancedCustomShape2d::SetEnhancedCustomShapeHandleParameter( aCenter.First, pData->nCenterX,
744 bool( nFlags & SvxMSDffHandleFlags::CENTER_X_IS_SPECIAL ), true );
745 EnhancedCustomShape2d::SetEnhancedCustomShapeHandleParameter( aCenter.Second, pData->nCenterY,
746 bool( nFlags & SvxMSDffHandleFlags::CENTER_Y_IS_SPECIAL ), false );
747 pPropValues[ n ].Name = "Polar";
748 pPropValues[ n++ ].Value <<= aCenter;
749 if ( nFlags & SvxMSDffHandleFlags::RADIUS_RANGE )
751 if ( pData->nRangeXMin != DEFAULT_MINIMUM_SIGNED_COMPARE )
753 drawing::EnhancedCustomShapeParameter aRadiusRangeMinimum;
754 EnhancedCustomShape2d::SetEnhancedCustomShapeHandleParameter( aRadiusRangeMinimum, pData->nRangeXMin,
755 bool( nFlags & SvxMSDffHandleFlags::RANGE_X_MIN_IS_SPECIAL ), true );
756 pPropValues[ n ].Name = "RadiusRangeMinimum";
757 pPropValues[ n++ ].Value <<= aRadiusRangeMinimum;
759 if ( pData->nRangeXMax != DEFAULT_MAXIMUM_SIGNED_COMPARE )
761 drawing::EnhancedCustomShapeParameter aRadiusRangeMaximum;
762 EnhancedCustomShape2d::SetEnhancedCustomShapeHandleParameter( aRadiusRangeMaximum, pData->nRangeXMax,
763 bool( nFlags & SvxMSDffHandleFlags::RANGE_X_MAX_IS_SPECIAL ), false );
764 pPropValues[ n ].Name = "RadiusRangeMaximum";
765 pPropValues[ n++ ].Value <<= aRadiusRangeMaximum;
769 else if ( nFlags & SvxMSDffHandleFlags::RANGE )
771 if ( pData->nRangeXMin != DEFAULT_MINIMUM_SIGNED_COMPARE )
773 drawing::EnhancedCustomShapeParameter aRangeXMinimum;
774 EnhancedCustomShape2d::SetEnhancedCustomShapeHandleParameter( aRangeXMinimum, pData->nRangeXMin,
775 bool( nFlags & SvxMSDffHandleFlags::RANGE_X_MIN_IS_SPECIAL ), true );
776 pPropValues[ n ].Name = "RangeXMinimum";
777 pPropValues[ n++ ].Value <<= aRangeXMinimum;
779 if ( pData->nRangeXMax != DEFAULT_MAXIMUM_SIGNED_COMPARE )
781 drawing::EnhancedCustomShapeParameter aRangeXMaximum;
782 EnhancedCustomShape2d::SetEnhancedCustomShapeHandleParameter( aRangeXMaximum, pData->nRangeXMax,
783 bool( nFlags & SvxMSDffHandleFlags::RANGE_X_MAX_IS_SPECIAL ), false );
784 pPropValues[ n ].Name = "RangeXMaximum";
785 pPropValues[ n++ ].Value <<= aRangeXMaximum;
787 if ( pData->nRangeYMin != DEFAULT_MINIMUM_SIGNED_COMPARE )
789 drawing::EnhancedCustomShapeParameter aRangeYMinimum;
790 EnhancedCustomShape2d::SetEnhancedCustomShapeHandleParameter( aRangeYMinimum, pData->nRangeYMin,
791 bool( nFlags & SvxMSDffHandleFlags::RANGE_Y_MIN_IS_SPECIAL ), true );
792 pPropValues[ n ].Name = "RangeYMinimum";
793 pPropValues[ n++ ].Value <<= aRangeYMinimum;
795 if ( pData->nRangeYMax != DEFAULT_MAXIMUM_SIGNED_COMPARE )
797 drawing::EnhancedCustomShapeParameter aRangeYMaximum;
798 EnhancedCustomShape2d::SetEnhancedCustomShapeHandleParameter( aRangeYMaximum, pData->nRangeYMax,
799 bool( nFlags & SvxMSDffHandleFlags::RANGE_Y_MAX_IS_SPECIAL ), false );
800 pPropValues[ n ].Name = "RangeYMaximum";
801 pPropValues[ n++ ].Value <<= aRangeYMaximum;
806 std::unique_ptr<sdr::properties::BaseProperties> SdrObjCustomShape::CreateObjectSpecificProperties()
808 return std::make_unique<sdr::properties::CustomShapeProperties>(*this);
811 SdrObjCustomShape::SdrObjCustomShape(SdrModel& rSdrModel)
812 : SdrTextObj(rSdrModel)
813 , m_fObjectRotation(0.0)
814 , mbAdjustingTextFrameWidthAndHeight(false)
816 m_bClosedObj = true; // custom shapes may be filled
817 mbTextFrame = true;
820 SdrObjCustomShape::SdrObjCustomShape(SdrModel& rSdrModel, SdrObjCustomShape const & rSource)
821 : SdrTextObj(rSdrModel, rSource)
822 , m_fObjectRotation(0.0)
823 , mbAdjustingTextFrameWidthAndHeight(false)
825 m_bClosedObj = true; // custom shapes may be filled
826 mbTextFrame = true;
828 m_fObjectRotation = rSource.m_fObjectRotation;
829 mbAdjustingTextFrameWidthAndHeight = rSource.mbAdjustingTextFrameWidthAndHeight;
830 assert(!mbAdjustingTextFrameWidthAndHeight);
831 InvalidateRenderGeometry();
834 SdrObjCustomShape::~SdrObjCustomShape()
836 // delete buffered display geometry
837 InvalidateRenderGeometry();
840 void SdrObjCustomShape::MergeDefaultAttributes( const OUString* pType )
842 beans::PropertyValue aPropVal;
843 OUString sShapeType;
844 static constexpr OUString sType( u"Type"_ustr );
845 SdrCustomShapeGeometryItem aGeometryItem( GetMergedItem( SDRATTR_CUSTOMSHAPE_GEOMETRY ) );
846 if ( pType && !pType->isEmpty() )
848 sal_Int32 nType = pType->toInt32();
849 if ( nType )
850 sShapeType = EnhancedCustomShapeTypeNames::Get( static_cast< MSO_SPT >( nType ) );
851 else
852 sShapeType = *pType;
854 aPropVal.Name = sType;
855 aPropVal.Value <<= sShapeType;
856 aGeometryItem.SetPropertyValue( aPropVal );
858 else
860 uno::Any *pAny = aGeometryItem.GetPropertyValueByName( sType );
861 if ( pAny )
862 *pAny >>= sShapeType;
864 MSO_SPT eSpType = EnhancedCustomShapeTypeNames::Get( sShapeType );
866 const sal_Int32* pDefData = nullptr;
867 const mso_CustomShape* pDefCustomShape = GetCustomShapeContent( eSpType );
868 if ( pDefCustomShape )
869 pDefData = pDefCustomShape->pDefData;
871 uno::Sequence<drawing::EnhancedCustomShapeAdjustmentValue> seqAdjustmentValues;
874 // AdjustmentValues
876 static constexpr OUString sAdjustmentValues( u"AdjustmentValues"_ustr );
877 const uno::Any* pAny = aGeometryItem.GetPropertyValueByName( sAdjustmentValues );
878 if ( pAny )
879 *pAny >>= seqAdjustmentValues;
880 if ( pDefCustomShape && pDefData ) // now check if we have to default some adjustment values
882 // first check if there are adjustment values are to be appended
883 sal_Int32 i, nAdjustmentValues = seqAdjustmentValues.getLength();
884 sal_Int32 nAdjustmentDefaults = *pDefData++;
885 if ( nAdjustmentDefaults > nAdjustmentValues )
886 seqAdjustmentValues.realloc( nAdjustmentDefaults );
887 auto pseqAdjustmentValues = seqAdjustmentValues.getArray();
888 for ( i = nAdjustmentValues; i < nAdjustmentDefaults; i++ )
890 pseqAdjustmentValues[ i ].Value <<= pDefData[ i ];
891 pseqAdjustmentValues[ i ].State = beans::PropertyState_DIRECT_VALUE;
893 // check if there are defaulted adjustment values that should be filled the hard coded defaults (pDefValue)
894 sal_Int32 nCount = std::min(nAdjustmentValues, nAdjustmentDefaults);
895 for ( i = 0; i < nCount; i++ )
897 if ( seqAdjustmentValues[ i ].State != beans::PropertyState_DIRECT_VALUE )
899 pseqAdjustmentValues[ i ].Value <<= pDefData[ i ];
900 pseqAdjustmentValues[ i ].State = beans::PropertyState_DIRECT_VALUE;
904 aPropVal.Name = sAdjustmentValues;
905 aPropVal.Value <<= seqAdjustmentValues;
906 aGeometryItem.SetPropertyValue( aPropVal );
909 // Coordsize
911 static constexpr OUString sViewBox( u"ViewBox"_ustr );
912 const uno::Any* pViewBox = aGeometryItem.GetPropertyValueByName( sViewBox );
913 awt::Rectangle aViewBox;
914 if ( !pViewBox || !(*pViewBox >>= aViewBox ) )
916 if ( pDefCustomShape )
918 aViewBox.X = 0;
919 aViewBox.Y = 0;
920 aViewBox.Width = pDefCustomShape->nCoordWidth;
921 aViewBox.Height= pDefCustomShape->nCoordHeight;
922 aPropVal.Name = sViewBox;
923 aPropVal.Value <<= aViewBox;
924 aGeometryItem.SetPropertyValue( aPropVal );
928 static constexpr OUString sPath( u"Path"_ustr );
931 // Path/Coordinates
933 static constexpr OUString sCoordinates( u"Coordinates"_ustr );
934 pAny = aGeometryItem.GetPropertyValueByName( sPath, sCoordinates );
935 if (!pAny && pDefCustomShape && !pDefCustomShape->pVertices.empty())
937 sal_Int32 i, nCount = pDefCustomShape->pVertices.size();
938 uno::Sequence<drawing::EnhancedCustomShapeParameterPair> seqCoordinates( nCount );
939 auto pseqCoordinates = seqCoordinates.getArray();
940 for ( i = 0; i < nCount; i++ )
942 EnhancedCustomShape2d::SetEnhancedCustomShapeParameter( pseqCoordinates[ i ].First, pDefCustomShape->pVertices[ i ].nValA );
943 EnhancedCustomShape2d::SetEnhancedCustomShapeParameter( pseqCoordinates[ i ].Second, pDefCustomShape->pVertices[ i ].nValB );
945 aPropVal.Name = sCoordinates;
946 aPropVal.Value <<= seqCoordinates;
947 aGeometryItem.SetPropertyValue( sPath, aPropVal );
950 // Path/GluePoints
951 static constexpr OUString sGluePoints( u"GluePoints"_ustr );
952 pAny = aGeometryItem.GetPropertyValueByName( sPath, sGluePoints );
953 if (!pAny && pDefCustomShape && !pDefCustomShape->pGluePoints.empty())
955 sal_Int32 i, nCount = pDefCustomShape->pGluePoints.size();
956 uno::Sequence<drawing::EnhancedCustomShapeParameterPair> seqGluePoints( nCount );
957 auto pseqGluePoints = seqGluePoints.getArray();
958 for ( i = 0; i < nCount; i++ )
960 EnhancedCustomShape2d::SetEnhancedCustomShapeParameter( pseqGluePoints[ i ].First, pDefCustomShape->pGluePoints[ i ].nValA );
961 EnhancedCustomShape2d::SetEnhancedCustomShapeParameter( pseqGluePoints[ i ].Second, pDefCustomShape->pGluePoints[ i ].nValB );
963 aPropVal.Name = sGluePoints;
964 aPropVal.Value <<= seqGluePoints;
965 aGeometryItem.SetPropertyValue( sPath, aPropVal );
968 // Path/Segments
969 static constexpr OUString sSegments( u"Segments"_ustr );
970 pAny = aGeometryItem.GetPropertyValueByName( sPath, sSegments );
971 if ( !pAny && pDefCustomShape && pDefCustomShape->nElements && pDefCustomShape->pElements )
973 sal_Int32 i, nCount = pDefCustomShape->nElements;
974 uno::Sequence<drawing::EnhancedCustomShapeSegment> seqSegments( nCount );
975 auto pseqSegments = seqSegments.getArray();
976 for ( i = 0; i < nCount; i++ )
978 drawing::EnhancedCustomShapeSegment& rSegInfo = pseqSegments[ i ];
979 sal_uInt16 nSDat = pDefCustomShape->pElements[ i ];
980 lcl_ShapeSegmentFromBinary( rSegInfo, nSDat );
982 aPropVal.Name = sSegments;
983 aPropVal.Value <<= seqSegments;
984 aGeometryItem.SetPropertyValue( sPath, aPropVal );
987 // Path/StretchX
988 static constexpr OUString sStretchX( u"StretchX"_ustr );
989 pAny = aGeometryItem.GetPropertyValueByName( sPath, sStretchX );
990 if ( !pAny && pDefCustomShape )
992 sal_Int32 nXRef = pDefCustomShape->nXRef;
993 if ( nXRef != DEFAULT_MINIMUM_SIGNED_COMPARE )
995 aPropVal.Name = sStretchX;
996 aPropVal.Value <<= nXRef;
997 aGeometryItem.SetPropertyValue( sPath, aPropVal );
1001 // Path/StretchY
1002 static constexpr OUString sStretchY( u"StretchY"_ustr );
1003 pAny = aGeometryItem.GetPropertyValueByName( sPath, sStretchY );
1004 if ( !pAny && pDefCustomShape )
1006 sal_Int32 nYRef = pDefCustomShape->nYRef;
1007 if ( nYRef != DEFAULT_MINIMUM_SIGNED_COMPARE )
1009 aPropVal.Name = sStretchY;
1010 aPropVal.Value <<= nYRef;
1011 aGeometryItem.SetPropertyValue( sPath, aPropVal );
1015 // Path/TextFrames
1016 static constexpr OUString sTextFrames( u"TextFrames"_ustr );
1017 pAny = aGeometryItem.GetPropertyValueByName( sPath, sTextFrames );
1018 if (!pAny && pDefCustomShape && !pDefCustomShape->pTextRect.empty())
1020 sal_Int32 i, nCount = pDefCustomShape->pTextRect.size();
1021 uno::Sequence<drawing::EnhancedCustomShapeTextFrame> seqTextFrames( nCount );
1022 auto pseqTextFrames = seqTextFrames.getArray();
1023 for (i = 0; i < nCount; i++)
1025 const SvxMSDffTextRectangles* pRectangles = &pDefCustomShape->pTextRect[i];
1026 EnhancedCustomShape2d::SetEnhancedCustomShapeParameter( pseqTextFrames[ i ].TopLeft.First, pRectangles->nPairA.nValA );
1027 EnhancedCustomShape2d::SetEnhancedCustomShapeParameter( pseqTextFrames[ i ].TopLeft.Second, pRectangles->nPairA.nValB );
1028 EnhancedCustomShape2d::SetEnhancedCustomShapeParameter( pseqTextFrames[ i ].BottomRight.First, pRectangles->nPairB.nValA );
1029 EnhancedCustomShape2d::SetEnhancedCustomShapeParameter( pseqTextFrames[ i ].BottomRight.Second, pRectangles->nPairB.nValB );
1031 aPropVal.Name = sTextFrames;
1032 aPropVal.Value <<= seqTextFrames;
1033 aGeometryItem.SetPropertyValue( sPath, aPropVal );
1036 // Equations
1037 static constexpr OUString sEquations( u"Equations"_ustr );
1038 pAny = aGeometryItem.GetPropertyValueByName( sEquations );
1039 if (!pAny && pDefCustomShape && !pDefCustomShape->pCalculation.empty() )
1041 sal_Int32 i, nCount = pDefCustomShape->pCalculation.size();
1042 uno::Sequence< OUString > seqEquations( nCount );
1043 auto pseqEquations = seqEquations.getArray();
1044 for (i = 0; i < nCount; i++)
1046 const SvxMSDffCalculationData* pData = &pDefCustomShape->pCalculation[i];
1047 pseqEquations[ i ] = EnhancedCustomShape2d::GetEquation( pData->nFlags, pData->nVal[ 0 ], pData->nVal[ 1 ], pData->nVal[ 2 ] );
1049 aPropVal.Name = sEquations;
1050 aPropVal.Value <<= seqEquations;
1051 aGeometryItem.SetPropertyValue( aPropVal );
1054 // Handles
1055 static constexpr OUString sHandles( u"Handles"_ustr );
1056 pAny = aGeometryItem.GetPropertyValueByName( sHandles );
1057 if (!pAny && pDefCustomShape && !pDefCustomShape->pHandles.empty())
1059 sal_Int32 i, nCount = pDefCustomShape->pHandles.size();
1060 uno::Sequence<beans::PropertyValues> seqHandles( nCount );
1061 auto pseqHandles = seqHandles.getArray();
1062 for (i = 0; i < nCount; i++)
1064 const SvxMSDffHandle* pData = &pDefCustomShape->pHandles[i];
1065 sal_Int32 nPropertiesNeeded;
1066 beans::PropertyValues& rPropValues = pseqHandles[ i ];
1067 nPropertiesNeeded = GetNumberOfProperties( pData );
1068 rPropValues.realloc( nPropertiesNeeded );
1069 lcl_ShapePropertiesFromDFF( pData, rPropValues );
1071 aPropVal.Name = sHandles;
1072 aPropVal.Value <<= seqHandles;
1073 aGeometryItem.SetPropertyValue( aPropVal );
1075 else if (pAny && sShapeType.startsWith("ooxml-") && sShapeType != "ooxml-non-primitive")
1077 // ODF is not able to store the ooxml way of connecting handle to an adjustment
1078 // value by name, e.g. attribute RefX="adj". So the information is lost, when exporting
1079 // a pptx to odp, for example. This part reconstructs this information for the
1080 // ooxml preset shapes from their definition.
1081 uno::Sequence<beans::PropertyValues> seqHandles;
1082 *pAny >>= seqHandles;
1083 auto seqHandlesRange = asNonConstRange(seqHandles);
1084 bool bChanged(false);
1085 for (sal_Int32 i = 0; i < seqHandles.getLength(); i++)
1087 comphelper::SequenceAsHashMap aHandleProps(seqHandles[i]);
1088 OUString sFirstRefType;
1089 sal_Int32 nFirstAdjRef;
1090 OUString sSecondRefType;
1091 sal_Int32 nSecondAdjRef;
1092 PresetOOXHandleAdj::GetOOXHandleAdjRelation(sShapeType, i, sFirstRefType, nFirstAdjRef,
1093 sSecondRefType, nSecondAdjRef);
1094 if (sFirstRefType != "na" && 0 <= nFirstAdjRef
1095 && nFirstAdjRef < seqAdjustmentValues.getLength())
1097 bChanged |= aHandleProps.createItemIfMissing(sFirstRefType, nFirstAdjRef);
1099 if (sSecondRefType != "na" && 0 <= nSecondAdjRef
1100 && nSecondAdjRef < seqAdjustmentValues.getLength())
1102 bChanged |= aHandleProps.createItemIfMissing(sSecondRefType, nSecondAdjRef);
1104 aHandleProps >> seqHandlesRange[i];
1106 if (bChanged)
1108 aPropVal.Name = sHandles;
1109 aPropVal.Value <<= seqHandles;
1110 aGeometryItem.SetPropertyValue(aPropVal);
1114 SetMergedItem( aGeometryItem );
1117 bool SdrObjCustomShape::IsDefaultGeometry( const DefaultType eDefaultType ) const
1119 bool bIsDefaultGeometry = false;
1121 OUString sShapeType;
1122 const SdrCustomShapeGeometryItem & rGeometryItem( GetMergedItem( SDRATTR_CUSTOMSHAPE_GEOMETRY ) );
1124 const uno::Any *pAny = rGeometryItem.GetPropertyValueByName( u"Type"_ustr );
1125 if ( pAny )
1126 *pAny >>= sShapeType;
1128 MSO_SPT eSpType = EnhancedCustomShapeTypeNames::Get( sShapeType );
1130 const mso_CustomShape* pDefCustomShape = GetCustomShapeContent( eSpType );
1131 static constexpr OUString sPath( u"Path"_ustr );
1132 switch( eDefaultType )
1134 case DefaultType::Viewbox :
1136 const uno::Any* pViewBox = rGeometryItem.GetPropertyValueByName( u"ViewBox"_ustr );
1137 awt::Rectangle aViewBox;
1138 if (pViewBox && (*pViewBox >>= aViewBox) && pDefCustomShape)
1140 if ( ( aViewBox.Width == pDefCustomShape->nCoordWidth )
1141 && ( aViewBox.Height == pDefCustomShape->nCoordHeight ) )
1142 bIsDefaultGeometry = true;
1145 break;
1147 case DefaultType::Path :
1149 pAny = rGeometryItem.GetPropertyValueByName( sPath, u"Coordinates"_ustr );
1150 if (pAny && pDefCustomShape && !pDefCustomShape->pVertices.empty())
1152 uno::Sequence<drawing::EnhancedCustomShapeParameterPair> seqCoordinates1;
1153 if ( *pAny >>= seqCoordinates1 )
1155 sal_Int32 i, nCount = pDefCustomShape->pVertices.size();
1156 uno::Sequence<drawing::EnhancedCustomShapeParameterPair> seqCoordinates2( nCount );
1157 auto pseqCoordinates2 = seqCoordinates2.getArray();
1158 for ( i = 0; i < nCount; i++ )
1160 EnhancedCustomShape2d::SetEnhancedCustomShapeParameter( pseqCoordinates2[ i ].First, pDefCustomShape->pVertices[ i ].nValA );
1161 EnhancedCustomShape2d::SetEnhancedCustomShapeParameter( pseqCoordinates2[ i ].Second, pDefCustomShape->pVertices[ i ].nValB );
1163 if ( seqCoordinates1 == seqCoordinates2 )
1164 bIsDefaultGeometry = true;
1167 else if (pDefCustomShape && pDefCustomShape->pVertices.empty())
1168 bIsDefaultGeometry = true;
1170 break;
1172 case DefaultType::Gluepoints :
1174 pAny = rGeometryItem.GetPropertyValueByName( sPath, u"GluePoints"_ustr );
1175 if (pAny && pDefCustomShape && !pDefCustomShape->pGluePoints.empty())
1177 uno::Sequence<drawing::EnhancedCustomShapeParameterPair> seqGluePoints1;
1178 if ( *pAny >>= seqGluePoints1 )
1180 sal_Int32 i, nCount = pDefCustomShape->pGluePoints.size();
1181 uno::Sequence<drawing::EnhancedCustomShapeParameterPair> seqGluePoints2( nCount );
1182 auto pseqGluePoints2 = seqGluePoints2.getArray();
1183 for ( i = 0; i < nCount; i++ )
1185 EnhancedCustomShape2d::SetEnhancedCustomShapeParameter( pseqGluePoints2[ i ].First, pDefCustomShape->pGluePoints[ i ].nValA );
1186 EnhancedCustomShape2d::SetEnhancedCustomShapeParameter( pseqGluePoints2[ i ].Second, pDefCustomShape->pGluePoints[ i ].nValB );
1188 if ( seqGluePoints1 == seqGluePoints2 )
1189 bIsDefaultGeometry = true;
1192 else if (pDefCustomShape && pDefCustomShape->pGluePoints.empty())
1193 bIsDefaultGeometry = true;
1195 break;
1197 case DefaultType::Segments :
1199 // Path/Segments
1200 pAny = rGeometryItem.GetPropertyValueByName( sPath, u"Segments"_ustr );
1201 if ( pAny )
1203 uno::Sequence<drawing::EnhancedCustomShapeSegment> seqSegments1;
1204 if ( *pAny >>= seqSegments1 )
1206 if ( pDefCustomShape && pDefCustomShape->nElements && pDefCustomShape->pElements )
1208 sal_Int32 i, nCount = pDefCustomShape->nElements;
1209 if ( nCount )
1211 uno::Sequence<drawing::EnhancedCustomShapeSegment> seqSegments2( nCount );
1212 auto pseqSegments2 = seqSegments2.getArray();
1213 for ( i = 0; i < nCount; i++ )
1215 drawing::EnhancedCustomShapeSegment& rSegInfo = pseqSegments2[ i ];
1216 sal_uInt16 nSDat = pDefCustomShape->pElements[ i ];
1217 lcl_ShapeSegmentFromBinary( rSegInfo, nSDat );
1219 if ( seqSegments1 == seqSegments2 )
1220 bIsDefaultGeometry = true;
1223 else
1225 // check if it's the default segment description ( M L Z N )
1226 if ( seqSegments1.getLength() == 4 )
1228 if ( ( seqSegments1[ 0 ].Command == drawing::EnhancedCustomShapeSegmentCommand::MOVETO )
1229 && ( seqSegments1[ 1 ].Command == drawing::EnhancedCustomShapeSegmentCommand::LINETO )
1230 && ( seqSegments1[ 2 ].Command == drawing::EnhancedCustomShapeSegmentCommand::CLOSESUBPATH )
1231 && ( seqSegments1[ 3 ].Command == drawing::EnhancedCustomShapeSegmentCommand::ENDSUBPATH ) )
1232 bIsDefaultGeometry = true;
1237 else if ( pDefCustomShape && ( ( pDefCustomShape->nElements == 0 ) || ( pDefCustomShape->pElements == nullptr ) ) )
1238 bIsDefaultGeometry = true;
1240 break;
1242 case DefaultType::StretchX :
1244 pAny = rGeometryItem.GetPropertyValueByName( sPath, u"StretchX"_ustr );
1245 if ( pAny && pDefCustomShape )
1247 sal_Int32 nStretchX = 0;
1248 if ( *pAny >>= nStretchX )
1250 if ( pDefCustomShape->nXRef == nStretchX )
1251 bIsDefaultGeometry = true;
1254 else if ( pDefCustomShape && ( pDefCustomShape->nXRef == DEFAULT_MINIMUM_SIGNED_COMPARE ) )
1255 bIsDefaultGeometry = true;
1257 break;
1259 case DefaultType::StretchY :
1261 pAny = rGeometryItem.GetPropertyValueByName( sPath, u"StretchY"_ustr );
1262 if ( pAny && pDefCustomShape )
1264 sal_Int32 nStretchY = 0;
1265 if ( *pAny >>= nStretchY )
1267 if ( pDefCustomShape->nYRef == nStretchY )
1268 bIsDefaultGeometry = true;
1271 else if ( pDefCustomShape && ( pDefCustomShape->nYRef == DEFAULT_MINIMUM_SIGNED_COMPARE ) )
1272 bIsDefaultGeometry = true;
1274 break;
1276 case DefaultType::Equations :
1278 pAny = rGeometryItem.GetPropertyValueByName( u"Equations"_ustr );
1279 if (pAny && pDefCustomShape && !pDefCustomShape->pCalculation.empty())
1281 uno::Sequence<OUString> seqEquations1;
1282 if ( *pAny >>= seqEquations1 )
1284 sal_Int32 i, nCount = pDefCustomShape->pCalculation.size();
1285 uno::Sequence<OUString> seqEquations2( nCount );
1286 auto pseqEquations2 = seqEquations2.getArray();
1288 for (i = 0; i < nCount; i++)
1290 const SvxMSDffCalculationData* pData = &pDefCustomShape->pCalculation[i];
1291 pseqEquations2[ i ] = EnhancedCustomShape2d::GetEquation( pData->nFlags, pData->nVal[ 0 ], pData->nVal[ 1 ], pData->nVal[ 2 ] );
1294 if ( seqEquations1 == seqEquations2 )
1295 bIsDefaultGeometry = true;
1298 else if (pDefCustomShape && pDefCustomShape->pCalculation.empty())
1299 bIsDefaultGeometry = true;
1301 break;
1303 case DefaultType::TextFrames :
1305 pAny = rGeometryItem.GetPropertyValueByName( sPath, u"TextFrames"_ustr );
1306 if (pAny && pDefCustomShape && !pDefCustomShape->pTextRect.empty())
1308 uno::Sequence<drawing::EnhancedCustomShapeTextFrame> seqTextFrames1;
1309 if ( *pAny >>= seqTextFrames1 )
1311 sal_Int32 i, nCount = pDefCustomShape->pTextRect.size();
1312 uno::Sequence<drawing::EnhancedCustomShapeTextFrame> seqTextFrames2( nCount );
1313 auto pseqTextFrames2 = seqTextFrames2.getArray();
1314 for (i = 0; i < nCount; i++)
1316 const SvxMSDffTextRectangles* pRectangles = &pDefCustomShape->pTextRect[i];
1317 EnhancedCustomShape2d::SetEnhancedCustomShapeParameter( pseqTextFrames2[ i ].TopLeft.First, pRectangles->nPairA.nValA );
1318 EnhancedCustomShape2d::SetEnhancedCustomShapeParameter( pseqTextFrames2[ i ].TopLeft.Second, pRectangles->nPairA.nValB );
1319 EnhancedCustomShape2d::SetEnhancedCustomShapeParameter( pseqTextFrames2[ i ].BottomRight.First, pRectangles->nPairB.nValA );
1320 EnhancedCustomShape2d::SetEnhancedCustomShapeParameter( pseqTextFrames2[ i ].BottomRight.Second, pRectangles->nPairB.nValB );
1322 if ( seqTextFrames1 == seqTextFrames2 )
1323 bIsDefaultGeometry = true;
1326 else if (pDefCustomShape && pDefCustomShape->pTextRect.empty())
1327 bIsDefaultGeometry = true;
1329 break;
1331 return bIsDefaultGeometry;
1334 void SdrObjCustomShape::TakeObjInfo(SdrObjTransformInfoRec& rInfo) const
1336 rInfo.bResizeFreeAllowed=m_fObjectRotation == 0.0;
1337 rInfo.bResizePropAllowed=true;
1338 rInfo.bRotateFreeAllowed=true;
1339 rInfo.bRotate90Allowed =true;
1340 rInfo.bMirrorFreeAllowed=true;
1341 rInfo.bMirror45Allowed =true;
1342 rInfo.bMirror90Allowed =true;
1343 rInfo.bTransparenceAllowed = false;
1344 rInfo.bShearAllowed =true;
1345 rInfo.bEdgeRadiusAllowed=false;
1346 rInfo.bNoContortion =true;
1348 // #i37011#
1349 if ( !mXRenderedCustomShape.is() )
1350 return;
1352 const SdrObject* pRenderedCustomShape = SdrObject::getSdrObjectFromXShape( mXRenderedCustomShape );
1353 if ( !pRenderedCustomShape )
1354 return;
1356 // #i37262#
1357 // Iterate self over the contained objects, since there are combinations of
1358 // polygon and curve objects. In that case, aInfo.bCanConvToPath and
1359 // aInfo.bCanConvToPoly would be false. What is needed here is an or, not an and.
1360 SdrObjListIter aIterator(*pRenderedCustomShape);
1361 while(aIterator.IsMore())
1363 SdrObject* pCandidate = aIterator.Next();
1364 SdrObjTransformInfoRec aInfo;
1365 pCandidate->TakeObjInfo(aInfo);
1367 // set path and poly conversion if one is possible since
1368 // this object will first be broken
1369 const bool bCanConvToPathOrPoly(aInfo.bCanConvToPath || aInfo.bCanConvToPoly);
1370 if(rInfo.bCanConvToPath != bCanConvToPathOrPoly)
1372 rInfo.bCanConvToPath = bCanConvToPathOrPoly;
1375 if(rInfo.bCanConvToPoly != bCanConvToPathOrPoly)
1377 rInfo.bCanConvToPoly = bCanConvToPathOrPoly;
1380 if(rInfo.bCanConvToContour != aInfo.bCanConvToContour)
1382 rInfo.bCanConvToContour = aInfo.bCanConvToContour;
1385 if(rInfo.bShearAllowed != aInfo.bShearAllowed)
1387 rInfo.bShearAllowed = aInfo.bShearAllowed;
1392 SdrObjKind SdrObjCustomShape::GetObjIdentifier() const
1394 return SdrObjKind::CustomShape;
1397 // #115391# This implementation is based on the TextFrame size of the CustomShape and the
1398 // state of the ResizeShapeToFitText flag to correctly set TextMinFrameWidth/Height
1399 void SdrObjCustomShape::AdaptTextMinSize()
1401 if (getSdrModelFromSdrObject().IsCreatingDataObj() || getSdrModelFromSdrObject().IsPasteResize())
1402 return;
1404 // check if we need to change anything before creating an SfxItemSet, because that is expensive
1405 const bool bResizeShapeToFitText(GetObjectItem(SDRATTR_TEXT_AUTOGROWHEIGHT).GetValue());
1406 tools::Rectangle aTextBound(getRectangle());
1407 bool bChanged(false);
1408 if(bResizeShapeToFitText)
1409 bChanged = true;
1410 else if(GetTextBounds(aTextBound))
1411 bChanged = true;
1412 if (!bChanged)
1413 return;
1415 SfxItemSetFixed<SDRATTR_TEXT_MINFRAMEHEIGHT, SDRATTR_TEXT_AUTOGROWHEIGHT,
1416 SDRATTR_TEXT_MINFRAMEWIDTH, SDRATTR_TEXT_AUTOGROWWIDTH> // contains SDRATTR_TEXT_MAXFRAMEWIDTH
1417 aSet(*GetObjectItemSet().GetPool());
1419 if(bResizeShapeToFitText)
1421 // always reset MinWidthHeight to zero to only rely on text size and frame size
1422 // to allow resizing being completely dependent on text size only
1423 aSet.Put(makeSdrTextMinFrameWidthItem(0));
1424 aSet.Put(makeSdrTextMinFrameHeightItem(0));
1426 else
1428 // recreate from CustomShape-specific TextBounds
1429 const tools::Long nHDist(GetTextLeftDistance() + GetTextRightDistance());
1430 const tools::Long nVDist(GetTextUpperDistance() + GetTextLowerDistance());
1431 const tools::Long nTWdt(std::max(tools::Long(0), static_cast<tools::Long>(aTextBound.GetWidth() - 1 - nHDist)));
1432 const tools::Long nTHgt(std::max(tools::Long(0), static_cast<tools::Long>(aTextBound.GetHeight() - 1 - nVDist)));
1434 aSet.Put(makeSdrTextMinFrameWidthItem(nTWdt));
1435 aSet.Put(makeSdrTextMinFrameHeightItem(nTHgt));
1438 SetObjectItemSet(aSet);
1441 void SdrObjCustomShape::NbcSetSnapRect(const tools::Rectangle& rRectangle)
1443 tools::Rectangle aRectangle(rRectangle);
1444 ImpJustifyRect(aRectangle);
1445 setRectangle(aRectangle);
1446 InvalidateRenderGeometry();
1448 AdaptTextMinSize();
1450 ImpCheckShear();
1451 SetBoundAndSnapRectsDirty();
1452 SetChanged();
1455 void SdrObjCustomShape::SetSnapRect( const tools::Rectangle& rRect )
1457 tools::Rectangle aBoundRect0;
1458 if ( m_pUserCall )
1459 aBoundRect0 = GetLastBoundRect();
1460 NbcSetSnapRect( rRect );
1461 BroadcastObjectChange();
1462 SendUserCall(SdrUserCallType::Resize,aBoundRect0);
1465 void SdrObjCustomShape::NbcSetLogicRect(const tools::Rectangle& rRectangle, bool bAdaptTextMinSize)
1467 tools::Rectangle aRectangle(rRectangle);
1468 ImpJustifyRect(aRectangle);
1469 setRectangle(aRectangle);
1470 InvalidateRenderGeometry();
1472 if (bAdaptTextMinSize)
1473 AdaptTextMinSize();
1475 SetBoundAndSnapRectsDirty();
1476 SetChanged();
1479 void SdrObjCustomShape::SetLogicRect( const tools::Rectangle& rRect )
1481 tools::Rectangle aBoundRect0;
1482 if ( m_pUserCall )
1483 aBoundRect0 = GetLastBoundRect();
1484 NbcSetLogicRect(rRect);
1485 BroadcastObjectChange();
1486 SendUserCall(SdrUserCallType::Resize,aBoundRect0);
1489 void SdrObjCustomShape::Move( const Size& rSiz )
1491 if ( rSiz.Width() || rSiz.Height() )
1493 tools::Rectangle aBoundRect0;
1494 if ( m_pUserCall )
1495 aBoundRect0 = GetLastBoundRect();
1496 NbcMove(rSiz);
1497 SetChanged();
1498 BroadcastObjectChange();
1499 SendUserCall(SdrUserCallType::MoveOnly,aBoundRect0);
1502 void SdrObjCustomShape::NbcMove( const Size& rSiz )
1504 SdrTextObj::NbcMove( rSiz );
1505 if ( mXRenderedCustomShape.is() )
1507 SdrObject* pRenderedCustomShape = SdrObject::getSdrObjectFromXShape(mXRenderedCustomShape);
1508 if ( pRenderedCustomShape )
1510 // #i97149# the visualisation shape needs to be informed
1511 // about change, too
1512 pRenderedCustomShape->ActionChanged();
1513 pRenderedCustomShape->NbcMove( rSiz );
1517 // #i37011# adapt geometry shadow
1518 if(mpLastShadowGeometry)
1520 mpLastShadowGeometry->NbcMove( rSiz );
1524 void SdrObjCustomShape::NbcResize( const Point& rRef, const Fraction& rxFact, const Fraction& ryFact )
1526 // taking care of handles that should not been changed
1527 tools::Rectangle aOld(getRectangle());
1528 std::vector< SdrCustomShapeInteraction > aInteractionHandles( GetInteractionHandles() );
1530 SdrTextObj::NbcResize( rRef, rxFact, ryFact );
1532 if ( ( rxFact.GetNumerator() != rxFact.GetDenominator() )
1533 || ( ryFact.GetNumerator()!= ryFact.GetDenominator() ) )
1535 if ( ( ( rxFact.GetNumerator() < 0 ) && ( rxFact.GetDenominator() > 0 ) ) ||
1536 ( ( rxFact.GetNumerator() > 0 ) && ( rxFact.GetDenominator() < 0 ) ) )
1538 SetMirroredX( !IsMirroredX() );
1540 if ( ( ( ryFact.GetNumerator() < 0 ) && ( ryFact.GetDenominator() > 0 ) ) ||
1541 ( ( ryFact.GetNumerator() > 0 ) && ( ryFact.GetDenominator() < 0 ) ) )
1543 SetMirroredY( !IsMirroredY() );
1547 for (const auto& rInteraction : aInteractionHandles)
1551 if ( rInteraction.nMode & CustomShapeHandleModes::RESIZE_FIXED )
1552 rInteraction.xInteraction->setControllerPosition( rInteraction.aPosition );
1553 if ( rInteraction.nMode & CustomShapeHandleModes::RESIZE_ABSOLUTE_X )
1555 sal_Int32 nX = ( rInteraction.aPosition.X - aOld.Left() ) + getRectangle().Left();
1556 rInteraction.xInteraction->setControllerPosition(awt::Point(nX, rInteraction.xInteraction->getPosition().Y));
1558 else if ( rInteraction.nMode & CustomShapeHandleModes::RESIZE_ABSOLUTE_NEGX )
1560 sal_Int32 nX = getRectangle().Right() - (aOld.Right() - rInteraction.aPosition.X);
1561 rInteraction.xInteraction->setControllerPosition(awt::Point(nX, rInteraction.xInteraction->getPosition().Y));
1563 if ( rInteraction.nMode & CustomShapeHandleModes::RESIZE_ABSOLUTE_Y )
1565 sal_Int32 nY = ( rInteraction.aPosition.Y - aOld.Top() ) + getRectangle().Top();
1566 rInteraction.xInteraction->setControllerPosition(awt::Point(rInteraction.xInteraction->getPosition().X, nY));
1569 catch ( const uno::RuntimeException& )
1574 // updating fObjectRotation
1575 Degree100 nTextObjRotation = maGeo.m_nRotationAngle;
1576 double fAngle = toDegrees(nTextObjRotation);
1577 if (IsMirroredX())
1579 if (IsMirroredY())
1580 m_fObjectRotation = fAngle - 180.0;
1581 else
1582 m_fObjectRotation = -fAngle;
1584 else
1586 if (IsMirroredY())
1587 m_fObjectRotation = 180.0 - fAngle;
1588 else
1589 m_fObjectRotation = fAngle;
1591 while (m_fObjectRotation < 0)
1592 m_fObjectRotation += 360.0;
1593 while (m_fObjectRotation >= 360.0)
1594 m_fObjectRotation -= 360.0;
1596 InvalidateRenderGeometry();
1599 void SdrObjCustomShape::NbcRotate( const Point& rRef, Degree100 nAngle, double sn, double cs )
1601 bool bMirroredX = IsMirroredX();
1602 bool bMirroredY = IsMirroredY();
1604 m_fObjectRotation = fmod( m_fObjectRotation, 360.0 );
1605 if ( m_fObjectRotation < 0 )
1606 m_fObjectRotation = 360 + m_fObjectRotation;
1608 // the rotation angle for ashapes is stored in fObjectRotation, this rotation
1609 // has to be applied to the text object (which is internally using maGeo.nAngle).
1610 SdrTextObj::NbcRotate( getRectangle().TopLeft(), -maGeo.m_nRotationAngle, // retrieving the unrotated text object
1611 -maGeo.mfSinRotationAngle,
1612 maGeo.mfCosRotationAngle );
1613 maGeo.m_nRotationAngle = 0_deg100; // resetting aGeo data
1614 maGeo.RecalcSinCos();
1616 Degree100 nW(static_cast<sal_Int32>( m_fObjectRotation * 100 )); // applying our object rotation
1617 if ( bMirroredX )
1618 nW = 36000_deg100 - nW;
1619 if ( bMirroredY )
1620 nW = 18000_deg100 - nW;
1621 nW = nW % 36000_deg100;
1622 if ( nW < 0_deg100 )
1623 nW = 36000_deg100 + nW;
1624 SdrTextObj::NbcRotate( getRectangle().TopLeft(), nW, // applying text rotation
1625 sin( toRadians(nW) ),
1626 cos( toRadians(nW) ) );
1628 int nSwap = 0;
1629 if ( bMirroredX )
1630 nSwap ^= 1;
1631 if ( bMirroredY )
1632 nSwap ^= 1;
1634 double fAngle = toDegrees(nAngle); // updating to our new object rotation
1635 m_fObjectRotation = fmod( nSwap ? m_fObjectRotation - fAngle : m_fObjectRotation + fAngle, 360.0 );
1636 if ( m_fObjectRotation < 0 )
1637 m_fObjectRotation = 360 + m_fObjectRotation;
1639 SdrTextObj::NbcRotate( rRef, nAngle, sn, cs ); // applying text rotation
1640 InvalidateRenderGeometry();
1643 void SdrObjCustomShape::NbcMirror( const Point& rRef1, const Point& rRef2 )
1645 // TTTT: Fix for old mirroring, can be removed again in aw080
1646 // storing horizontal and vertical flipping without modifying the rotate angle
1647 // decompose other flipping to rotation and MirrorX.
1648 tools::Long ndx = rRef2.X()-rRef1.X();
1649 tools::Long ndy = rRef2.Y()-rRef1.Y();
1651 if(!ndx) // MirroredX
1653 SetMirroredX(!IsMirroredX());
1654 SdrTextObj::NbcMirror( rRef1, rRef2 );
1656 else
1658 if(!ndy) // MirroredY
1660 SetMirroredY(!IsMirroredY());
1661 SdrTextObj::NbcMirror( rRef1, rRef2 );
1663 else // neither horizontal nor vertical
1665 SetMirroredX(!IsMirroredX());
1667 // call parent
1668 SdrTextObj::NbcMirror( rRef1, rRef2 );
1670 // update fObjectRotation
1671 Degree100 nTextObjRotation = maGeo.m_nRotationAngle;
1672 double fAngle = toDegrees(nTextObjRotation);
1674 bool bSingleFlip = (IsMirroredX()!= IsMirroredY());
1676 m_fObjectRotation = fmod( bSingleFlip ? -fAngle : fAngle, 360.0 );
1678 if ( m_fObjectRotation < 0 )
1680 m_fObjectRotation = 360.0 + m_fObjectRotation;
1685 InvalidateRenderGeometry();
1688 void SdrObjCustomShape::Shear( const Point& rRef, Degree100 nAngle, double tn, bool bVShear )
1690 SdrTextObj::Shear( rRef, nAngle, tn, bVShear );
1691 InvalidateRenderGeometry();
1693 void SdrObjCustomShape::NbcShear( const Point& rRef, Degree100 nAngle, double tn, bool bVShear )
1695 // TTTT: Fix for old mirroring, can be removed again in aw080
1696 SdrTextObj::NbcShear(rRef,nAngle,tn,bVShear);
1698 // updating fObjectRotation
1699 Degree100 nTextObjRotation = maGeo.m_nRotationAngle;
1700 double fAngle = toDegrees(nTextObjRotation);
1701 if (IsMirroredX())
1703 if (IsMirroredY())
1704 m_fObjectRotation = fAngle - 180.0;
1705 else
1706 m_fObjectRotation = -fAngle;
1708 else
1710 if (IsMirroredY())
1711 m_fObjectRotation = 180.0 - fAngle;
1712 else
1713 m_fObjectRotation = fAngle;
1715 while (m_fObjectRotation < 0)
1716 m_fObjectRotation += 360.0;
1717 while (m_fObjectRotation >= 360.0)
1718 m_fObjectRotation -= 360.0;
1720 InvalidateRenderGeometry();
1723 SdrGluePoint SdrObjCustomShape::GetVertexGluePoint(sal_uInt16 nPosNum) const
1725 sal_Int32 nWdt = ImpGetLineWdt(); // #i25616#
1727 // #i25616#
1728 if(!LineIsOutsideGeometry())
1730 nWdt++;
1731 nWdt /= 2;
1734 Point aPt;
1735 tools::Rectangle aRectangle = getRectangle();
1736 switch (nPosNum)
1738 case 0: aPt = aRectangle.TopCenter(); aPt.AdjustY( -nWdt ); break;
1739 case 1: aPt = aRectangle.RightCenter(); aPt.AdjustX(nWdt ); break;
1740 case 2: aPt = aRectangle.BottomCenter(); aPt.AdjustY(nWdt ); break;
1741 case 3: aPt = aRectangle.LeftCenter(); aPt.AdjustX( -nWdt ); break;
1743 if (maGeo.m_nShearAngle != 0_deg100)
1744 ShearPoint(aPt, aRectangle.TopLeft(), maGeo.mfTanShearAngle);
1745 if (maGeo.m_nRotationAngle != 0_deg100)
1746 RotatePoint(aPt, aRectangle.TopLeft(), maGeo.mfSinRotationAngle, maGeo.mfCosRotationAngle);
1747 aPt-=GetSnapRect().Center();
1748 SdrGluePoint aGP(aPt);
1749 aGP.SetPercent(false);
1750 return aGP;
1754 // #i38892#
1755 void SdrObjCustomShape::ImpCheckCustomGluePointsAreAdded()
1757 const SdrObject* pSdrObject = GetSdrObjectFromCustomShape();
1759 if(!pSdrObject)
1760 return;
1762 const SdrGluePointList* pSource = pSdrObject->GetGluePointList();
1764 if(!(pSource && pSource->GetCount()))
1765 return;
1767 if(!SdrTextObj::GetGluePointList())
1769 SdrTextObj::ForceGluePointList();
1772 const SdrGluePointList* pList = SdrTextObj::GetGluePointList();
1774 if(!pList)
1775 return;
1777 SdrGluePointList aNewList;
1778 sal_uInt16 a;
1780 for(a = 0; a < pSource->GetCount(); a++)
1782 SdrGluePoint aCopy((*pSource)[a]);
1783 aCopy.SetUserDefined(false);
1784 aNewList.Insert(aCopy);
1787 bool bMirroredX = IsMirroredX();
1788 bool bMirroredY = IsMirroredY();
1790 Degree100 nShearAngle = maGeo.m_nShearAngle;
1791 double fTan = maGeo.mfTanShearAngle;
1793 if (maGeo.m_nRotationAngle || nShearAngle || bMirroredX || bMirroredY)
1795 tools::Polygon aPoly(getRectangle());
1796 if( nShearAngle )
1798 sal_uInt16 nPointCount=aPoly.GetSize();
1799 for (sal_uInt16 i=0; i<nPointCount; i++)
1800 ShearPoint(aPoly[i], getRectangle().Center(), fTan );
1802 if (maGeo.m_nRotationAngle)
1803 aPoly.Rotate( getRectangle().Center(), to<Degree10>(maGeo.m_nRotationAngle) );
1805 tools::Rectangle aBoundRect( aPoly.GetBoundRect() );
1806 sal_Int32 nXDiff = aBoundRect.Left() - getRectangle().Left();
1807 sal_Int32 nYDiff = aBoundRect.Top() - getRectangle().Top();
1809 if (nShearAngle && bMirroredX != bMirroredY)
1811 nShearAngle = -nShearAngle;
1812 fTan = -fTan;
1815 Point aRef( getRectangle().GetWidth() / 2, getRectangle().GetHeight() / 2 );
1816 for ( a = 0; a < aNewList.GetCount(); a++ )
1818 SdrGluePoint& rPoint = aNewList[ a ];
1819 Point aGlue( rPoint.GetPos() );
1820 if ( nShearAngle )
1821 ShearPoint( aGlue, aRef, fTan );
1823 RotatePoint(aGlue, aRef, sin(basegfx::deg2rad(m_fObjectRotation)),
1824 cos(basegfx::deg2rad(m_fObjectRotation)));
1825 if ( bMirroredX )
1826 aGlue.setX( getRectangle().GetWidth() - aGlue.X() );
1827 if ( bMirroredY )
1828 aGlue.setY( getRectangle().GetHeight() - aGlue.Y() );
1829 aGlue.AdjustX( -nXDiff );
1830 aGlue.AdjustY( -nYDiff );
1831 rPoint.SetPos( aGlue );
1835 for(a = 0; a < pList->GetCount(); a++)
1837 const SdrGluePoint& rCandidate = (*pList)[a];
1839 if(rCandidate.IsUserDefined())
1841 aNewList.Insert(rCandidate);
1845 // copy new list to local. This is NOT very convenient behavior, the local
1846 // GluePointList should not be set, but we delivered by using GetGluePointList(),
1847 // maybe on demand. Since the local object is changed here, this is assumed to
1848 // be a result of GetGluePointList and thus the list is copied
1849 if(m_pPlusData)
1851 m_pPlusData->SetGluePoints(aNewList);
1855 // #i38892#
1856 const SdrGluePointList* SdrObjCustomShape::GetGluePointList() const
1858 const_cast<SdrObjCustomShape*>(this)->ImpCheckCustomGluePointsAreAdded();
1859 return SdrTextObj::GetGluePointList();
1862 // #i38892#
1863 SdrGluePointList* SdrObjCustomShape::ForceGluePointList()
1865 if(SdrTextObj::ForceGluePointList())
1867 ImpCheckCustomGluePointsAreAdded();
1868 return SdrTextObj::ForceGluePointList();
1870 else
1872 return nullptr;
1877 sal_uInt32 SdrObjCustomShape::GetHdlCount() const
1879 const sal_uInt32 nBasicHdlCount(SdrTextObj::GetHdlCount());
1880 return ( GetInteractionHandles().size() + nBasicHdlCount );
1883 void SdrObjCustomShape::AddToHdlList(SdrHdlList& rHdlList) const
1885 SdrTextObj::AddToHdlList(rHdlList);
1887 int nCustomShapeHdlNum = 0;
1888 for (SdrCustomShapeInteraction const & rInteraction : GetInteractionHandles())
1890 if ( rInteraction.xInteraction.is() )
1894 awt::Point aPosition( rInteraction.xInteraction->getPosition() );
1895 std::unique_ptr<SdrHdl> pH(new SdrHdl( Point( aPosition.X, aPosition.Y ), SdrHdlKind::CustomShape1 ));
1896 pH->SetPointNum( nCustomShapeHdlNum );
1897 pH->SetObj( const_cast<SdrObjCustomShape*>(this) );
1898 rHdlList.AddHdl(std::move(pH));
1900 catch ( const uno::RuntimeException& )
1904 ++nCustomShapeHdlNum;
1908 bool SdrObjCustomShape::hasSpecialDrag() const
1910 return true;
1913 bool SdrObjCustomShape::beginSpecialDrag(SdrDragStat& rDrag) const
1915 const SdrHdl* pHdl = rDrag.GetHdl();
1917 if(pHdl && SdrHdlKind::CustomShape1 == pHdl->GetKind())
1919 rDrag.SetEndDragChangesAttributes(true);
1920 rDrag.SetNoSnap();
1922 else
1924 const SdrHdl* pHdl2 = rDrag.GetHdl();
1925 const SdrHdlKind eHdl((pHdl2 == nullptr) ? SdrHdlKind::Move : pHdl2->GetKind());
1927 switch( eHdl )
1929 case SdrHdlKind::UpperLeft :
1930 case SdrHdlKind::Upper :
1931 case SdrHdlKind::UpperRight :
1932 case SdrHdlKind::Left :
1933 case SdrHdlKind::Right :
1934 case SdrHdlKind::LowerLeft :
1935 case SdrHdlKind::Lower :
1936 case SdrHdlKind::LowerRight :
1937 case SdrHdlKind::Move :
1939 break;
1941 default:
1943 return false;
1948 return true;
1951 void SdrObjCustomShape::DragResizeCustomShape( const tools::Rectangle& rNewRect )
1953 tools::Rectangle aOld(getRectangle());
1954 bool bOldMirroredX( IsMirroredX() );
1955 bool bOldMirroredY( IsMirroredY() );
1957 tools::Rectangle aNewRect( rNewRect );
1958 aNewRect.Normalize();
1960 std::vector< SdrCustomShapeInteraction > aInteractionHandles( GetInteractionHandles() );
1962 GeoStat aGeoStat( GetGeoStat() );
1963 if ( aNewRect.TopLeft() != getRectangle().TopLeft() &&
1964 ( maGeo.m_nRotationAngle || maGeo.m_nShearAngle ) )
1966 Point aNewPos( aNewRect.TopLeft() );
1967 if ( maGeo.m_nShearAngle ) ShearPoint( aNewPos, aOld.TopLeft(), aGeoStat.mfTanShearAngle );
1968 if ( maGeo.m_nRotationAngle ) RotatePoint(aNewPos, aOld.TopLeft(), aGeoStat.mfSinRotationAngle, aGeoStat.mfCosRotationAngle );
1969 aNewRect.SetPos( aNewPos );
1971 if (aNewRect == getRectangle())
1972 return;
1974 SetLogicRect( aNewRect );
1975 InvalidateRenderGeometry();
1977 if ( rNewRect.Left() > rNewRect.Right() )
1979 Point aTop( ( GetSnapRect().Left() + GetSnapRect().Right() ) >> 1, GetSnapRect().Top() );
1980 Point aBottom( aTop.X(), aTop.Y() + 1000 );
1981 NbcMirror( aTop, aBottom );
1983 if ( rNewRect.Top() > rNewRect.Bottom() )
1985 Point aLeft( GetSnapRect().Left(), ( GetSnapRect().Top() + GetSnapRect().Bottom() ) >> 1 );
1986 Point aRight( aLeft.X() + 1000, aLeft.Y() );
1987 NbcMirror( aLeft, aRight );
1990 for (const auto& rInteraction : aInteractionHandles)
1994 if ( rInteraction.nMode & CustomShapeHandleModes::RESIZE_FIXED )
1995 rInteraction.xInteraction->setControllerPosition( rInteraction.aPosition );
1996 if ( rInteraction.nMode & CustomShapeHandleModes::RESIZE_ABSOLUTE_X ||
1997 rInteraction.nMode & CustomShapeHandleModes::RESIZE_ABSOLUTE_NEGX )
1999 if (rInteraction.nMode & CustomShapeHandleModes::RESIZE_ABSOLUTE_NEGX)
2000 bOldMirroredX = !bOldMirroredX;
2002 sal_Int32 nX;
2003 if ( bOldMirroredX )
2005 nX = ( rInteraction.aPosition.X - aOld.Right() );
2006 if ( rNewRect.Left() > rNewRect.Right() )
2007 nX = getRectangle().Left() - nX;
2008 else
2009 nX += getRectangle().Right();
2011 else
2013 nX = ( rInteraction.aPosition.X - aOld.Left() );
2014 if ( rNewRect.Left() > rNewRect.Right() )
2015 nX = getRectangle().Right() - nX;
2016 else
2017 nX += getRectangle().Left();
2019 rInteraction.xInteraction->setControllerPosition(awt::Point(nX, rInteraction.xInteraction->getPosition().Y));
2021 if ( rInteraction.nMode & CustomShapeHandleModes::RESIZE_ABSOLUTE_Y )
2023 sal_Int32 nY;
2024 if ( bOldMirroredY )
2026 nY = ( rInteraction.aPosition.Y - aOld.Bottom() );
2027 if ( rNewRect.Top() > rNewRect.Bottom() )
2028 nY = getRectangle().Top() - nY;
2029 else
2030 nY += getRectangle().Bottom();
2032 else
2034 nY = ( rInteraction.aPosition.Y - aOld.Top() );
2035 if ( rNewRect.Top() > rNewRect.Bottom() )
2036 nY = getRectangle().Bottom() - nY;
2037 else
2038 nY += getRectangle().Top();
2040 rInteraction.xInteraction->setControllerPosition(awt::Point(rInteraction.xInteraction->getPosition().X, nY));
2043 catch ( const uno::RuntimeException& )
2049 void SdrObjCustomShape::DragMoveCustomShapeHdl( const Point& rDestination,
2050 const sal_uInt16 nCustomShapeHdlNum, bool bMoveCalloutRectangle )
2052 std::vector< SdrCustomShapeInteraction > aInteractionHandles( GetInteractionHandles() );
2053 if ( nCustomShapeHdlNum >= aInteractionHandles.size() )
2054 return;
2056 SdrCustomShapeInteraction aInteractionHandle( aInteractionHandles[ nCustomShapeHdlNum ] );
2057 if ( !aInteractionHandle.xInteraction.is() )
2058 return;
2062 awt::Point aPt( rDestination.X(), rDestination.Y() );
2063 if ( aInteractionHandle.nMode & CustomShapeHandleModes::MOVE_SHAPE && bMoveCalloutRectangle )
2065 sal_Int32 nXDiff = aPt.X - aInteractionHandle.aPosition.X;
2066 sal_Int32 nYDiff = aPt.Y - aInteractionHandle.aPosition.Y;
2068 moveRectangle(nXDiff, nYDiff);
2069 moveOutRectangle(nXDiff, nYDiff);
2070 maSnapRect.Move( nXDiff, nYDiff );
2071 SetBoundAndSnapRectsDirty(/*bNotMyself*/true);
2072 InvalidateRenderGeometry();
2074 for (const auto& rInteraction : aInteractionHandles)
2076 if ( rInteraction.nMode & CustomShapeHandleModes::RESIZE_FIXED )
2078 if ( rInteraction.xInteraction.is() )
2079 rInteraction.xInteraction->setControllerPosition( rInteraction.aPosition );
2083 aInteractionHandle.xInteraction->setControllerPosition( aPt );
2085 catch ( const uno::RuntimeException& )
2090 bool SdrObjCustomShape::applySpecialDrag(SdrDragStat& rDrag)
2092 const SdrHdl* pHdl = rDrag.GetHdl();
2093 const SdrHdlKind eHdl((pHdl == nullptr) ? SdrHdlKind::Move : pHdl->GetKind());
2095 switch(eHdl)
2097 case SdrHdlKind::CustomShape1 :
2099 rDrag.SetEndDragChangesGeoAndAttributes(true);
2100 DragMoveCustomShapeHdl( rDrag.GetNow(), static_cast<sal_uInt16>(pHdl->GetPointNum()), !rDrag.GetDragMethod()->IsShiftPressed() );
2101 SetBoundAndSnapRectsDirty();
2102 InvalidateRenderGeometry();
2103 SetChanged();
2104 break;
2107 case SdrHdlKind::UpperLeft :
2108 case SdrHdlKind::Upper :
2109 case SdrHdlKind::UpperRight :
2110 case SdrHdlKind::Left :
2111 case SdrHdlKind::Right :
2112 case SdrHdlKind::LowerLeft :
2113 case SdrHdlKind::Lower :
2114 case SdrHdlKind::LowerRight :
2116 DragResizeCustomShape( ImpDragCalcRect(rDrag) );
2117 break;
2119 case SdrHdlKind::Move :
2121 Move(Size(rDrag.GetDX(), rDrag.GetDY()));
2122 break;
2124 default: break;
2127 return true;
2131 void SdrObjCustomShape::DragCreateObject( SdrDragStat& rStat )
2133 tools::Rectangle aRect1;
2134 rStat.TakeCreateRect( aRect1 );
2136 std::vector< SdrCustomShapeInteraction > aInteractionHandles( GetInteractionHandles() );
2138 constexpr sal_uInt32 nDefaultObjectSizeWidth = 3000; // default width from SDOptions ?
2139 constexpr sal_uInt32 nDefaultObjectSizeHeight= 3000;
2141 if ( ImpVerticalSwitch( *this ) )
2143 SetMirroredX( aRect1.Left() > aRect1.Right() );
2145 aRect1 = tools::Rectangle( rStat.GetNow(), Size( nDefaultObjectSizeWidth, nDefaultObjectSizeHeight ) );
2146 // subtracting the horizontal difference of the latest handle from shape position
2147 if ( !aInteractionHandles.empty() )
2149 sal_Int32 nHandlePos = aInteractionHandles[ aInteractionHandles.size() - 1 ].xInteraction->getPosition().X;
2150 aRect1.Move(getRectangle().Left() - nHandlePos, 0);
2153 ImpJustifyRect( aRect1 );
2154 rStat.SetActionRect( aRect1 );
2155 setRectangle(aRect1);
2156 SetBoundAndSnapRectsDirty();
2158 for (const auto& rInteraction : aInteractionHandles)
2162 if ( rInteraction.nMode & CustomShapeHandleModes::CREATE_FIXED )
2163 rInteraction.xInteraction->setControllerPosition( awt::Point( rStat.GetStart().X(), rStat.GetStart().Y() ) );
2165 catch ( const uno::RuntimeException& )
2170 SetBoundRectDirty();
2171 m_bSnapRectDirty=true;
2174 bool SdrObjCustomShape::MovCreate(SdrDragStat& rStat)
2176 SdrView* pView = rStat.GetView(); // #i37448#
2177 if( pView && pView->IsSolidDragging() )
2179 InvalidateRenderGeometry();
2181 DragCreateObject( rStat );
2182 SetBoundAndSnapRectsDirty();
2183 return true;
2186 bool SdrObjCustomShape::EndCreate( SdrDragStat& rStat, SdrCreateCmd eCmd )
2188 DragCreateObject( rStat );
2190 AdaptTextMinSize();
2192 SetBoundAndSnapRectsDirty();
2193 return ( eCmd == SdrCreateCmd::ForceEnd || rStat.GetPointCount() >= 2 );
2196 basegfx::B2DPolyPolygon SdrObjCustomShape::TakeCreatePoly(const SdrDragStat& /*rDrag*/) const
2198 return GetLineGeometry( false );
2202 // in context with the SdrObjCustomShape the SdrTextAutoGrowHeightItem == true -> Resize Shape to fit text,
2203 // the SdrTextAutoGrowWidthItem == true -> Word wrap text in Shape
2204 bool SdrObjCustomShape::IsAutoGrowHeight() const
2206 const SfxItemSet& rSet = GetMergedItemSet();
2207 bool bIsAutoGrowHeight = rSet.Get(SDRATTR_TEXT_AUTOGROWHEIGHT).GetValue();
2208 if ( bIsAutoGrowHeight && IsVerticalWriting() )
2209 bIsAutoGrowHeight = !rSet.Get(SDRATTR_TEXT_WORDWRAP).GetValue();
2210 return bIsAutoGrowHeight;
2212 bool SdrObjCustomShape::IsAutoGrowWidth() const
2214 const SfxItemSet& rSet = GetMergedItemSet();
2215 bool bIsAutoGrowWidth = rSet.Get(SDRATTR_TEXT_AUTOGROWHEIGHT).GetValue();
2216 if ( bIsAutoGrowWidth && !IsVerticalWriting() )
2217 bIsAutoGrowWidth = !rSet.Get(SDRATTR_TEXT_WORDWRAP).GetValue();
2218 return bIsAutoGrowWidth;
2221 /* The following method is identical to the SdrTextObj::SetVerticalWriting method, the only difference
2222 is that the SdrAutoGrowWidthItem and SdrAutoGrowHeightItem are not exchanged if the vertical writing
2223 mode has been changed */
2225 void SdrObjCustomShape::SetVerticalWriting( bool bVertical )
2227 ForceOutlinerParaObject();
2229 OutlinerParaObject* pOutlinerParaObject = GetOutlinerParaObject();
2231 DBG_ASSERT( pOutlinerParaObject, "SdrTextObj::SetVerticalWriting() without OutlinerParaObject!" );
2233 if( !pOutlinerParaObject ||
2234 (pOutlinerParaObject->IsEffectivelyVertical() == bVertical) )
2235 return;
2237 // get item settings
2238 const SfxItemSet& rSet = GetObjectItemSet();
2240 // Also exchange horizontal and vertical adjust items
2241 SdrTextHorzAdjust eHorz = rSet.Get(SDRATTR_TEXT_HORZADJUST).GetValue();
2242 SdrTextVertAdjust eVert = rSet.Get(SDRATTR_TEXT_VERTADJUST).GetValue();
2244 // rescue object size, SetSnapRect below expects logic rect,
2245 // not snap rect.
2246 tools::Rectangle aObjectRect = GetLogicRect();
2248 // prepare ItemSet to set exchanged width and height items
2249 SfxItemSetFixed<SDRATTR_TEXT_AUTOGROWHEIGHT, SDRATTR_TEXT_AUTOGROWHEIGHT,
2250 // Expanded item ranges to also support horizontal and vertical adjust.
2251 SDRATTR_TEXT_VERTADJUST, SDRATTR_TEXT_VERTADJUST,
2252 SDRATTR_TEXT_AUTOGROWWIDTH, SDRATTR_TEXT_HORZADJUST> aNewSet(*rSet.GetPool());
2254 aNewSet.Put(rSet);
2256 // Exchange horizontal and vertical adjusts
2257 switch(eVert)
2259 case SDRTEXTVERTADJUST_TOP: aNewSet.Put(SdrTextHorzAdjustItem(SDRTEXTHORZADJUST_RIGHT)); break;
2260 case SDRTEXTVERTADJUST_CENTER: aNewSet.Put(SdrTextHorzAdjustItem(SDRTEXTHORZADJUST_CENTER)); break;
2261 case SDRTEXTVERTADJUST_BOTTOM: aNewSet.Put(SdrTextHorzAdjustItem(SDRTEXTHORZADJUST_LEFT)); break;
2262 case SDRTEXTVERTADJUST_BLOCK: aNewSet.Put(SdrTextHorzAdjustItem(SDRTEXTHORZADJUST_BLOCK)); break;
2264 switch(eHorz)
2266 case SDRTEXTHORZADJUST_LEFT: aNewSet.Put(SdrTextVertAdjustItem(SDRTEXTVERTADJUST_BOTTOM)); break;
2267 case SDRTEXTHORZADJUST_CENTER: aNewSet.Put(SdrTextVertAdjustItem(SDRTEXTVERTADJUST_CENTER)); break;
2268 case SDRTEXTHORZADJUST_RIGHT: aNewSet.Put(SdrTextVertAdjustItem(SDRTEXTVERTADJUST_TOP)); break;
2269 case SDRTEXTHORZADJUST_BLOCK: aNewSet.Put(SdrTextVertAdjustItem(SDRTEXTVERTADJUST_BLOCK)); break;
2272 pOutlinerParaObject = GetOutlinerParaObject();
2273 if ( pOutlinerParaObject )
2274 pOutlinerParaObject->SetVertical(bVertical);
2275 SetObjectItemSet( aNewSet );
2277 // restore object size
2278 SetSnapRect(aObjectRect);
2281 void SdrObjCustomShape::SuggestTextFrameSize(Size aSuggestedTextFrameSize)
2283 m_aSuggestedTextFrameSize = aSuggestedTextFrameSize;
2286 bool SdrObjCustomShape::AdjustTextFrameWidthAndHeight(tools::Rectangle& rR, bool bHgt, bool bWdt) const
2288 // Either we have text or the application has native text and suggested its size to us.
2289 bool bHasText = HasText() || !m_aSuggestedTextFrameSize.IsEmpty();
2290 if ( bHasText && !rR.IsEmpty() )
2292 bool bWdtGrow=bWdt && IsAutoGrowWidth();
2293 bool bHgtGrow=bHgt && IsAutoGrowHeight();
2294 if ( bWdtGrow || bHgtGrow )
2296 tools::Rectangle aR0(rR);
2297 tools::Long nHgt=0,nMinHgt=0,nMaxHgt=0;
2298 tools::Long nWdt=0,nMinWdt=0,nMaxWdt=0;
2299 Size aSiz(rR.GetSize()); aSiz.AdjustWidth( -1 ); aSiz.AdjustHeight( -1 );
2300 Size aMaxSiz(100000,100000);
2301 Size aTmpSiz(getSdrModelFromSdrObject().GetMaxObjSize());
2302 if (aTmpSiz.Width()!=0) aMaxSiz.setWidth(aTmpSiz.Width() );
2303 if (aTmpSiz.Height()!=0) aMaxSiz.setHeight(aTmpSiz.Height() );
2304 if (bWdtGrow)
2306 nMinWdt=GetMinTextFrameWidth();
2307 nMaxWdt=GetMaxTextFrameWidth();
2308 if (nMaxWdt==0 || nMaxWdt>aMaxSiz.Width()) nMaxWdt=aMaxSiz.Width();
2309 if (nMinWdt<=0) nMinWdt=1;
2310 aSiz.setWidth(nMaxWdt );
2312 if (bHgtGrow)
2314 nMinHgt=GetMinTextFrameHeight();
2315 nMaxHgt=GetMaxTextFrameHeight();
2316 if (nMaxHgt==0 || nMaxHgt>aMaxSiz.Height()) nMaxHgt=aMaxSiz.Height();
2317 if (nMinHgt<=0) nMinHgt=1;
2318 aSiz.setHeight(nMaxHgt );
2320 tools::Long nHDist=GetTextLeftDistance()+GetTextRightDistance();
2321 tools::Long nVDist=GetTextUpperDistance()+GetTextLowerDistance();
2322 aSiz.AdjustWidth( -nHDist );
2323 aSiz.AdjustHeight( -nVDist );
2324 if ( aSiz.Width() < 2 )
2325 aSiz.setWidth( 2 ); // minimum size=2
2326 if ( aSiz.Height() < 2 )
2327 aSiz.setHeight( 2 ); // minimum size=2
2329 if (HasText())
2331 if(mpEditingOutliner)
2333 mpEditingOutliner->SetMaxAutoPaperSize( aSiz );
2334 if (bWdtGrow)
2336 Size aSiz2(mpEditingOutliner->CalcTextSize());
2337 nWdt=aSiz2.Width()+1; // a little more tolerance
2338 if (bHgtGrow) nHgt=aSiz2.Height()+1; // a little more tolerance
2339 } else
2341 nHgt=mpEditingOutliner->GetTextHeight()+1; // a little more tolerance
2344 else
2346 Outliner& rOutliner=ImpGetDrawOutliner();
2347 rOutliner.SetPaperSize(aSiz);
2348 rOutliner.SetUpdateLayout(true);
2349 // TODO: add the optimization with bPortionInfoChecked again.
2350 OutlinerParaObject* pOutlinerParaObject = GetOutlinerParaObject();
2351 if( pOutlinerParaObject != nullptr )
2353 rOutliner.SetText(*pOutlinerParaObject);
2354 rOutliner.SetFixedCellHeight(GetMergedItem(SDRATTR_TEXT_USEFIXEDCELLHEIGHT).GetValue());
2356 if ( bWdtGrow )
2358 Size aSiz2(rOutliner.CalcTextSize());
2359 nWdt=aSiz2.Width()+1; // a little more tolerance
2360 if ( bHgtGrow )
2361 nHgt=aSiz2.Height()+1; // a little more tolerance
2363 else
2365 nHgt = rOutliner.GetTextHeight()+1; // a little more tolerance
2367 sal_Int16 nColumns = GetMergedItem(SDRATTR_TEXTCOLUMNS_NUMBER).GetValue();
2368 if (bHgtGrow && nColumns > 1)
2370 // Both 'resize shape to fix text' and multiple columns are enabled. The
2371 // first means a dynamic height, the second expects a fixed height.
2372 // Resolve this conflict by going with the original height.
2373 nHgt = rR.getOpenHeight();
2376 rOutliner.Clear();
2379 else
2381 nHgt = m_aSuggestedTextFrameSize.Height();
2382 nWdt = m_aSuggestedTextFrameSize.Width();
2384 if ( nWdt < nMinWdt )
2385 nWdt = nMinWdt;
2386 if ( nWdt > nMaxWdt )
2387 nWdt = nMaxWdt;
2388 nWdt += nHDist;
2389 if ( nWdt < 1 )
2390 nWdt = 1; // nHDist may also be negative
2391 if ( nHgt < nMinHgt )
2392 nHgt = nMinHgt;
2393 if ( nHgt > nMaxHgt )
2394 nHgt = nMaxHgt;
2395 nHgt+=nVDist;
2396 if ( nHgt < 1 )
2397 nHgt = 1; // nVDist may also be negative
2398 tools::Long nWdtGrow = nWdt-(rR.Right()-rR.Left());
2399 tools::Long nHgtGrow = nHgt-(rR.Bottom()-rR.Top());
2400 if ( nWdtGrow == 0 )
2401 bWdtGrow = false;
2402 if ( nHgtGrow == 0 )
2403 bHgtGrow=false;
2404 if ( bWdtGrow || bHgtGrow || !m_aSuggestedTextFrameSize.IsEmpty())
2406 if ( bWdtGrow || m_aSuggestedTextFrameSize.Width() )
2408 SdrTextHorzAdjust eHAdj=GetTextHorizontalAdjust();
2409 if (m_aSuggestedTextFrameSize.Width())
2411 rR.SetRight(rR.Left() + m_aSuggestedTextFrameSize.Width());
2413 else if ( eHAdj == SDRTEXTHORZADJUST_LEFT )
2414 rR.AdjustRight(nWdtGrow );
2415 else if ( eHAdj == SDRTEXTHORZADJUST_RIGHT )
2416 rR.AdjustLeft( -nWdtGrow );
2417 else
2419 tools::Long nWdtGrow2=nWdtGrow/2;
2420 rR.AdjustLeft( -nWdtGrow2 );
2421 rR.SetRight(rR.Left()+nWdt );
2424 if ( bHgtGrow || m_aSuggestedTextFrameSize.Height() )
2426 SdrTextVertAdjust eVAdj=GetTextVerticalAdjust();
2427 if (m_aSuggestedTextFrameSize.Height())
2429 rR.SetBottom(rR.Top() + m_aSuggestedTextFrameSize.Height());
2431 else if ( eVAdj == SDRTEXTVERTADJUST_TOP )
2432 rR.AdjustBottom(nHgtGrow );
2433 else if ( eVAdj == SDRTEXTVERTADJUST_BOTTOM )
2434 rR.AdjustTop( -nHgtGrow );
2435 else
2437 tools::Long nHgtGrow2=nHgtGrow/2;
2438 rR.AdjustTop( -nHgtGrow2 );
2439 rR.SetBottom(rR.Top()+nHgt );
2442 if ( maGeo.m_nRotationAngle )
2444 Point aD1(rR.TopLeft());
2445 aD1-=aR0.TopLeft();
2446 Point aD2(aD1);
2447 RotatePoint(aD2,Point(), maGeo.mfSinRotationAngle, maGeo.mfCosRotationAngle);
2448 aD2-=aD1;
2449 rR.Move(aD2.X(),aD2.Y());
2451 return true;
2455 return false;
2458 tools::Rectangle SdrObjCustomShape::ImpCalculateTextFrame( const bool bHgt, const bool bWdt )
2460 tools::Rectangle aReturnValue;
2462 tools::Rectangle aOldTextRect(getRectangle()); // <- initial text rectangle
2464 tools::Rectangle aNewTextRect(getRectangle()); // <- new text rectangle returned from the custom shape renderer,
2465 GetTextBounds( aNewTextRect ); // it depends to the current logical shape size
2467 tools::Rectangle aAdjustedTextRect( aNewTextRect ); // <- new text rectangle is being tested by AdjustTextFrameWidthAndHeight to ensure
2468 if ( AdjustTextFrameWidthAndHeight( aAdjustedTextRect, bHgt, bWdt ) ) // that the new text rectangle is matching the current text size from the outliner
2470 if (aAdjustedTextRect != aNewTextRect && aOldTextRect != aAdjustedTextRect &&
2471 aNewTextRect.GetWidth() && aNewTextRect.GetHeight())
2473 aReturnValue = getRectangle();
2474 double fXScale = static_cast<double>(aOldTextRect.GetWidth()) / static_cast<double>(aNewTextRect.GetWidth());
2475 double fYScale = static_cast<double>(aOldTextRect.GetHeight()) / static_cast<double>(aNewTextRect.GetHeight());
2476 double fRightDiff = static_cast<double>( aAdjustedTextRect.Right() - aNewTextRect.Right() ) * fXScale;
2477 double fLeftDiff = static_cast<double>( aAdjustedTextRect.Left() - aNewTextRect.Left() ) * fXScale;
2478 double fTopDiff = static_cast<double>( aAdjustedTextRect.Top() - aNewTextRect.Top() ) * fYScale;
2479 double fBottomDiff= static_cast<double>( aAdjustedTextRect.Bottom()- aNewTextRect.Bottom()) * fYScale;
2480 aReturnValue.AdjustLeft(static_cast<sal_Int32>(fLeftDiff) );
2481 aReturnValue.AdjustRight(static_cast<sal_Int32>(fRightDiff) );
2482 aReturnValue.AdjustTop(static_cast<sal_Int32>(fTopDiff) );
2483 aReturnValue.AdjustBottom(static_cast<sal_Int32>(fBottomDiff) );
2486 return aReturnValue;
2489 bool SdrObjCustomShape::NbcAdjustTextFrameWidthAndHeight(bool bHgt, bool bWdt)
2491 tools::Rectangle aNewTextRect = ImpCalculateTextFrame(bHgt, bWdt);
2492 const bool bRet = !aNewTextRect.IsEmpty() && aNewTextRect != getRectangle();
2493 if (bRet && !mbAdjustingTextFrameWidthAndHeight)
2495 mbAdjustingTextFrameWidthAndHeight = true;
2497 // taking care of handles that should not been changed
2498 std::vector< SdrCustomShapeInteraction > aInteractionHandles( GetInteractionHandles() );
2500 setRectangle(aNewTextRect);
2501 SetBoundAndSnapRectsDirty();
2502 SetChanged();
2504 for (const auto& rInteraction : aInteractionHandles)
2508 if ( rInteraction.nMode & CustomShapeHandleModes::RESIZE_FIXED )
2509 rInteraction.xInteraction->setControllerPosition( rInteraction.aPosition );
2511 catch ( const uno::RuntimeException& )
2515 InvalidateRenderGeometry();
2517 mbAdjustingTextFrameWidthAndHeight = false;
2519 return bRet;
2522 bool SdrObjCustomShape::AdjustTextFrameWidthAndHeight()
2524 tools::Rectangle aNewTextRect = ImpCalculateTextFrame( true/*bHgt*/, true/*bWdt*/ );
2525 bool bRet = !aNewTextRect.IsEmpty() && ( aNewTextRect != getRectangle());
2526 if ( bRet )
2528 tools::Rectangle aBoundRect0;
2529 if ( m_pUserCall )
2530 aBoundRect0 = GetCurrentBoundRect();
2532 // taking care of handles that should not been changed
2533 std::vector< SdrCustomShapeInteraction > aInteractionHandles( GetInteractionHandles() );
2535 setRectangle(aNewTextRect);
2536 SetBoundAndSnapRectsDirty();
2538 for (const auto& rInteraction : aInteractionHandles)
2542 if ( rInteraction.nMode & CustomShapeHandleModes::RESIZE_FIXED )
2543 rInteraction.xInteraction->setControllerPosition( rInteraction.aPosition );
2545 catch ( const uno::RuntimeException& )
2550 InvalidateRenderGeometry();
2551 SetChanged();
2552 BroadcastObjectChange();
2553 SendUserCall(SdrUserCallType::Resize,aBoundRect0);
2555 return bRet;
2557 void SdrObjCustomShape::TakeTextEditArea(Size* pPaperMin, Size* pPaperMax, tools::Rectangle* pViewInit, tools::Rectangle* pViewMin) const
2559 tools::Rectangle aViewInit;
2560 TakeTextAnchorRect( aViewInit );
2561 if (maGeo.m_nRotationAngle)
2563 Point aCenter(aViewInit.Center());
2564 aCenter-=aViewInit.TopLeft();
2565 Point aCenter0(aCenter);
2566 RotatePoint(aCenter, Point(), maGeo.mfSinRotationAngle, maGeo.mfCosRotationAngle);
2567 aCenter-=aCenter0;
2568 aViewInit.Move(aCenter.X(),aCenter.Y());
2570 Size aAnkSiz(aViewInit.GetSize());
2571 aAnkSiz.AdjustWidth( -1 ); aAnkSiz.AdjustHeight( -1 ); // because GetSize() adds 1
2572 Size aMaxSiz(1000000,1000000);
2574 Size aTmpSiz(getSdrModelFromSdrObject().GetMaxObjSize());
2575 if (aTmpSiz.Width()!=0) aMaxSiz.setWidth(aTmpSiz.Width() );
2576 if (aTmpSiz.Height()!=0) aMaxSiz.setHeight(aTmpSiz.Height() );
2578 SdrTextHorzAdjust eHAdj(GetTextHorizontalAdjust());
2579 SdrTextVertAdjust eVAdj(GetTextVerticalAdjust());
2581 tools::Long nMinWdt = GetMinTextFrameWidth();
2582 tools::Long nMinHgt = GetMinTextFrameHeight();
2583 tools::Long nMaxWdt = GetMaxTextFrameWidth();
2584 tools::Long nMaxHgt = GetMaxTextFrameHeight();
2585 if (nMinWdt<1) nMinWdt=1;
2586 if (nMinHgt<1) nMinHgt=1;
2587 if ( nMaxWdt == 0 || nMaxWdt > aMaxSiz.Width() )
2588 nMaxWdt = aMaxSiz.Width();
2589 if ( nMaxHgt == 0 || nMaxHgt > aMaxSiz.Height() )
2590 nMaxHgt=aMaxSiz.Height();
2592 if (GetMergedItem(SDRATTR_TEXT_WORDWRAP).GetValue())
2594 if ( IsVerticalWriting() )
2596 nMaxHgt = aAnkSiz.Height();
2597 nMinHgt = nMaxHgt;
2599 else
2601 nMaxWdt = aAnkSiz.Width();
2602 nMinWdt = nMaxWdt;
2605 Size aPaperMax(nMaxWdt, nMaxHgt);
2606 Size aPaperMin(nMinWdt, nMinHgt);
2608 if ( pViewMin )
2610 *pViewMin = aViewInit;
2612 tools::Long nXFree = aAnkSiz.Width() - aPaperMin.Width();
2613 if ( eHAdj == SDRTEXTHORZADJUST_LEFT )
2614 pViewMin->AdjustRight( -nXFree );
2615 else if ( eHAdj == SDRTEXTHORZADJUST_RIGHT )
2616 pViewMin->AdjustLeft(nXFree );
2617 else { pViewMin->AdjustLeft(nXFree / 2 ); pViewMin->SetRight( pViewMin->Left() + aPaperMin.Width() ); }
2619 tools::Long nYFree = aAnkSiz.Height() - aPaperMin.Height();
2620 if ( eVAdj == SDRTEXTVERTADJUST_TOP )
2621 pViewMin->AdjustBottom( -nYFree );
2622 else if ( eVAdj == SDRTEXTVERTADJUST_BOTTOM )
2623 pViewMin->AdjustTop(nYFree );
2624 else { pViewMin->AdjustTop(nYFree / 2 ); pViewMin->SetBottom( pViewMin->Top() + aPaperMin.Height() ); }
2627 if( IsVerticalWriting() )
2628 aPaperMin.setWidth( 0 );
2629 else
2630 aPaperMin.setHeight( 0 );
2632 if( eHAdj != SDRTEXTHORZADJUST_BLOCK )
2633 aPaperMin.setWidth(0 );
2635 // For complete vertical adjust support, set paper min height to 0, here.
2636 if(SDRTEXTVERTADJUST_BLOCK != eVAdj )
2637 aPaperMin.setHeight( 0 );
2639 if (pPaperMin!=nullptr) *pPaperMin=aPaperMin;
2640 if (pPaperMax!=nullptr) *pPaperMax=aPaperMax;
2641 if (pViewInit!=nullptr) *pViewInit=aViewInit;
2643 void SdrObjCustomShape::EndTextEdit( SdrOutliner& rOutl )
2645 SdrTextObj::EndTextEdit( rOutl );
2646 InvalidateRenderGeometry();
2648 void SdrObjCustomShape::TakeTextAnchorRect( tools::Rectangle& rAnchorRect ) const
2650 if ( GetTextBounds( rAnchorRect ) )
2652 Point aRotateRef( maSnapRect.Center() );
2653 const double fExtraTextRotation(GetExtraTextRotation());
2654 AdjustRectToTextDistance(rAnchorRect, fExtraTextRotation);
2656 if ( rAnchorRect.GetWidth() < 2 )
2657 rAnchorRect.SetRight( rAnchorRect.Left() + 1 ); // minimal width is 2
2658 if ( rAnchorRect.GetHeight() < 2 )
2659 rAnchorRect.SetBottom( rAnchorRect.Top() + 1 ); // minimal height is 2
2660 if (maGeo.m_nRotationAngle)
2662 Point aP( rAnchorRect.TopLeft() );
2663 RotatePoint(aP, aRotateRef, maGeo.mfSinRotationAngle, maGeo.mfCosRotationAngle);
2664 rAnchorRect.SetPos( aP );
2667 else
2668 SdrTextObj::TakeTextAnchorRect( rAnchorRect );
2670 void SdrObjCustomShape::TakeTextRect( SdrOutliner& rOutliner, tools::Rectangle& rTextRect, bool bNoEditText,
2671 tools::Rectangle* pAnchorRect, bool /*bLineWidth*/) const
2673 tools::Rectangle aAnkRect; // Rect in which we anchor
2674 TakeTextAnchorRect(aAnkRect);
2675 SdrTextVertAdjust eVAdj=GetTextVerticalAdjust();
2676 SdrTextHorzAdjust eHAdj=GetTextHorizontalAdjust();
2677 EEControlBits nStat0=rOutliner.GetControlWord();
2678 Size aNullSize;
2680 rOutliner.SetControlWord(nStat0|EEControlBits::AUTOPAGESIZE);
2681 rOutliner.SetMinAutoPaperSize(aNullSize);
2682 sal_Int32 nMaxAutoPaperWidth = 1000000;
2683 sal_Int32 nMaxAutoPaperHeight= 1000000;
2685 tools::Long nAnkWdt=aAnkRect.GetWidth();
2686 tools::Long nAnkHgt=aAnkRect.GetHeight();
2688 if (GetMergedItem(SDRATTR_TEXT_WORDWRAP).GetValue())
2690 if ( IsVerticalWriting() )
2691 nMaxAutoPaperHeight = nAnkHgt;
2692 else
2693 nMaxAutoPaperWidth = nAnkWdt;
2695 if(SDRTEXTHORZADJUST_BLOCK == eHAdj && !IsVerticalWriting())
2697 rOutliner.SetMinAutoPaperSize(Size(nAnkWdt, 0));
2700 if(SDRTEXTVERTADJUST_BLOCK == eVAdj && IsVerticalWriting())
2702 rOutliner.SetMinAutoPaperSize(Size(0, nAnkHgt));
2704 rOutliner.SetMaxAutoPaperSize( Size( nMaxAutoPaperWidth, nMaxAutoPaperHeight ) );
2705 rOutliner.SetPaperSize( aNullSize );
2707 // put text into the Outliner - if necessary the use the text from the EditOutliner
2708 std::optional<OutlinerParaObject> pPara;
2709 if (GetOutlinerParaObject())
2710 pPara = *GetOutlinerParaObject();
2711 if (mpEditingOutliner && !bNoEditText)
2712 pPara=mpEditingOutliner->CreateParaObject();
2714 if (pPara)
2716 bool bHitTest(&getSdrModelFromSdrObject().GetHitTestOutliner() == &rOutliner);
2717 const SdrTextObj* pTestObj = rOutliner.GetTextObj();
2719 if( !pTestObj || !bHitTest || pTestObj != this ||
2720 pTestObj->GetOutlinerParaObject() != GetOutlinerParaObject() )
2722 if( bHitTest )
2723 rOutliner.SetTextObj( this );
2725 rOutliner.SetUpdateLayout(true);
2726 rOutliner.SetText(*pPara);
2729 else
2731 rOutliner.SetTextObj( nullptr );
2734 rOutliner.SetUpdateLayout(true);
2735 rOutliner.SetControlWord(nStat0);
2737 SdrText* pText = getActiveText();
2738 if( pText )
2739 pText->CheckPortionInfo( rOutliner );
2741 Point aTextPos(aAnkRect.TopLeft());
2742 Size aTextSiz(rOutliner.GetPaperSize()); // GetPaperSize() has a little added tolerance, no?
2744 // For draw objects containing text correct horizontal/vertical alignment if text is bigger
2745 // than the object itself. Without that correction, the text would always be
2746 // formatted to the left edge (or top edge when vertical) of the draw object.
2748 if( !IsTextFrame() )
2750 if(aAnkRect.GetWidth() < aTextSiz.Width() && !IsVerticalWriting())
2752 // Horizontal case here. Correct only if eHAdj == SDRTEXTHORZADJUST_BLOCK,
2753 // else the alignment is wanted.
2754 if(SDRTEXTHORZADJUST_BLOCK == eHAdj)
2756 SvxAdjust eAdjust = GetObjectItemSet().Get(EE_PARA_JUST).GetAdjust();
2757 switch (eAdjust)
2759 case SvxAdjust::Left: eHAdj = SDRTEXTHORZADJUST_LEFT; break;
2760 case SvxAdjust::Right: eHAdj = SDRTEXTHORZADJUST_RIGHT; break;
2761 case SvxAdjust::Center: eHAdj = SDRTEXTHORZADJUST_CENTER; break;
2762 default: break;
2767 if(aAnkRect.GetHeight() < aTextSiz.Height() && IsVerticalWriting())
2769 // Vertical case here. Correct only if eHAdj == SDRTEXTVERTADJUST_BLOCK,
2770 // else the alignment is wanted.
2771 if(SDRTEXTVERTADJUST_BLOCK == eVAdj)
2773 eVAdj = SDRTEXTVERTADJUST_CENTER;
2778 if (eHAdj==SDRTEXTHORZADJUST_CENTER || eHAdj==SDRTEXTHORZADJUST_RIGHT)
2780 tools::Long nFreeWdt=aAnkRect.GetWidth()-aTextSiz.Width();
2781 if (eHAdj==SDRTEXTHORZADJUST_CENTER)
2782 aTextPos.AdjustX(nFreeWdt/2 );
2783 if (eHAdj==SDRTEXTHORZADJUST_RIGHT)
2784 aTextPos.AdjustX(nFreeWdt );
2786 if (eVAdj==SDRTEXTVERTADJUST_CENTER || eVAdj==SDRTEXTVERTADJUST_BOTTOM)
2788 tools::Long nFreeHgt=aAnkRect.GetHeight()-aTextSiz.Height();
2789 if (eVAdj==SDRTEXTVERTADJUST_CENTER)
2790 aTextPos.AdjustY(nFreeHgt/2 );
2791 if (eVAdj==SDRTEXTVERTADJUST_BOTTOM)
2792 aTextPos.AdjustY(nFreeHgt );
2794 if (maGeo.m_nRotationAngle != 0_deg100)
2795 RotatePoint(aTextPos,aAnkRect.TopLeft(), maGeo.mfSinRotationAngle, maGeo.mfCosRotationAngle);
2797 if (pAnchorRect)
2798 *pAnchorRect=aAnkRect;
2800 // using rTextRect together with ContourFrame doesn't always work correctly
2801 rTextRect=tools::Rectangle(aTextPos,aTextSiz);
2804 void SdrObjCustomShape::NbcSetOutlinerParaObject(std::optional<OutlinerParaObject> pTextObject, bool bAdjustTextFrameWidthAndHeight)
2806 SdrTextObj::NbcSetOutlinerParaObject( std::move(pTextObject), bAdjustTextFrameWidthAndHeight );
2807 SetBoundRectDirty();
2808 SetBoundAndSnapRectsDirty(true);
2809 InvalidateRenderGeometry();
2812 rtl::Reference<SdrObject> SdrObjCustomShape::CloneSdrObject(SdrModel& rTargetModel) const
2814 return new SdrObjCustomShape(rTargetModel, *this);
2817 OUString SdrObjCustomShape::TakeObjNameSingul() const
2819 OUString sName(SvxResId(STR_ObjNameSingulCUSTOMSHAPE));
2820 OUString aNm(GetName());
2821 if (!aNm.isEmpty())
2822 sName += " '" + aNm + "'";
2823 return sName;
2826 OUString SdrObjCustomShape::TakeObjNamePlural() const
2828 return SvxResId(STR_ObjNamePluralCUSTOMSHAPE);
2831 basegfx::B2DPolyPolygon SdrObjCustomShape::TakeXorPoly() const
2833 return GetLineGeometry( false );
2836 basegfx::B2DPolyPolygon SdrObjCustomShape::TakeContour() const
2838 const SdrObject* pSdrObject = GetSdrObjectFromCustomShape();
2839 if ( pSdrObject )
2840 return pSdrObject->TakeContour();
2841 return basegfx::B2DPolyPolygon();
2844 rtl::Reference<SdrObject> SdrObjCustomShape::DoConvertToPolyObj(bool bBezier, bool bAddText) const
2846 // #i37011#
2847 rtl::Reference<SdrObject> pRetval;
2848 SdrObject* pRenderedCustomShape = nullptr;
2850 if ( !mXRenderedCustomShape.is() )
2852 // force CustomShape
2853 GetSdrObjectFromCustomShape();
2856 if ( mXRenderedCustomShape.is() )
2858 pRenderedCustomShape = SdrObject::getSdrObjectFromXShape(mXRenderedCustomShape);
2861 if ( pRenderedCustomShape )
2863 // Clone to same SdrModel
2864 rtl::Reference<SdrObject> pCandidate(pRenderedCustomShape->CloneSdrObject(pRenderedCustomShape->getSdrModelFromSdrObject()));
2865 DBG_ASSERT(pCandidate, "SdrObjCustomShape::DoConvertToPolyObj: Could not clone SdrObject (!)");
2866 pRetval = pCandidate->DoConvertToPolyObj(bBezier, bAddText);
2867 pCandidate.clear();
2869 if(pRetval)
2871 const bool bShadow(GetMergedItem(SDRATTR_SHADOW).GetValue());
2872 if(bShadow)
2874 pRetval->SetMergedItem(makeSdrShadowItem(true));
2878 if(bAddText && HasText() && !IsTextPath())
2880 pRetval = ImpConvertAddText(std::move(pRetval), bBezier);
2884 return pRetval;
2887 void SdrObjCustomShape::InternalSetStyleSheet( SfxStyleSheet* pNewStyleSheet, bool bDontRemoveHardAttr, bool bBroadcast, bool bAdjustTextFrameWidthAndHeight )
2889 // #i40944#
2890 InvalidateRenderGeometry();
2891 SdrObject::InternalSetStyleSheet( pNewStyleSheet, bDontRemoveHardAttr, bBroadcast, bAdjustTextFrameWidthAndHeight );
2894 void SdrObjCustomShape::handlePageChange(SdrPage* pOldPage, SdrPage* pNewPage)
2896 // call parent
2897 SdrTextObj::handlePageChange(pOldPage, pNewPage);
2899 if(nullptr != pNewPage)
2901 // invalidating rectangles by SetRectsDirty is not sufficient,
2902 // AdjustTextFrameWidthAndHeight() also has to be made, both
2903 // actions are done by NbcSetSnapRect
2904 tools::Rectangle aRectangle(getRectangle()); //creating temporary rectangle #i61108#
2905 NbcSetSnapRect(aRectangle);
2909 std::unique_ptr<SdrObjGeoData> SdrObjCustomShape::NewGeoData() const
2911 return std::make_unique<SdrAShapeObjGeoData>();
2914 void SdrObjCustomShape::SaveGeoData(SdrObjGeoData& rGeo) const
2916 SdrTextObj::SaveGeoData( rGeo );
2917 SdrAShapeObjGeoData& rAGeo=static_cast<SdrAShapeObjGeoData&>(rGeo);
2918 rAGeo.fObjectRotation = m_fObjectRotation;
2919 rAGeo.bMirroredX = IsMirroredX();
2920 rAGeo.bMirroredY = IsMirroredY();
2922 const uno::Any* pAny = GetMergedItem( SDRATTR_CUSTOMSHAPE_GEOMETRY ).GetPropertyValueByName( u"AdjustmentValues"_ustr );
2923 if ( pAny )
2924 *pAny >>= rAGeo.aAdjustmentSeq;
2927 void SdrObjCustomShape::RestoreGeoData(const SdrObjGeoData& rGeo)
2929 SdrTextObj::RestoreGeoData( rGeo );
2930 const SdrAShapeObjGeoData& rAGeo=static_cast<const SdrAShapeObjGeoData&>(rGeo);
2931 m_fObjectRotation = rAGeo.fObjectRotation;
2932 SetMirroredX( rAGeo.bMirroredX );
2933 SetMirroredY( rAGeo.bMirroredY );
2935 SdrCustomShapeGeometryItem rGeometryItem = GetMergedItem( SDRATTR_CUSTOMSHAPE_GEOMETRY );
2936 beans::PropertyValue aPropVal;
2937 aPropVal.Name = "AdjustmentValues";
2938 aPropVal.Value <<= rAGeo.aAdjustmentSeq;
2939 rGeometryItem.SetPropertyValue( aPropVal );
2940 SetMergedItem( rGeometryItem );
2942 InvalidateRenderGeometry();
2945 void SdrObjCustomShape::AdjustToMaxRect(const tools::Rectangle& rMaxRect, bool bShrinkOnly /* = false */)
2947 SAL_INFO_IF(bShrinkOnly, "svx", "Case bShrinkOnly == true is not implemented yet.");
2949 if (rMaxRect.IsEmpty() || rMaxRect == GetSnapRect())
2950 return;
2952 // Get a matrix, that would produce the existing shape, when applied to a unit square
2953 basegfx::B2DPolyPolygon aPolyPolygon; //not used, but formal needed
2954 basegfx::B2DHomMatrix aMatrix;
2955 TRGetBaseGeometry(aMatrix, aPolyPolygon);
2956 // Using TRSetBaseGeometry(aMatrix, aPolyPolygon) would regenerate the current shape. But
2957 // applying aMatrix to a unit square will not generate the current shape. Scaling,
2958 // rotation and translation are correct, but shear angle has wrong sign. So break up
2959 // matrix and create a mathematically correct new one.
2960 basegfx::B2DTuple aScale;
2961 basegfx::B2DTuple aTranslate;
2962 double fRotate, fShearX;
2963 aMatrix.decompose(aScale, aTranslate, fRotate, fShearX);
2964 basegfx::B2DHomMatrix aMathMatrix;
2965 aMathMatrix = basegfx::utils::createScaleShearXRotateTranslateB2DHomMatrix(
2966 aScale,
2967 basegfx::fTools::equalZero(fShearX) ? 0.0 : -fShearX,
2968 basegfx::fTools::equalZero(fRotate) ? 0.0 : fRotate,
2969 aTranslate);
2971 // Calculate scaling factors from size of the transformed unit polygon as ersatz for the not
2972 // usable current snap rectangle.
2973 basegfx::B2DPolygon aB2DPolygon(basegfx::utils::createUnitPolygon());
2974 aB2DPolygon.transform(aMathMatrix);
2975 basegfx::B2DRange aB2DRange(aB2DPolygon.getB2DRange());
2976 double fPolygonWidth = aB2DRange.getWidth();
2977 if (fPolygonWidth == 0)
2978 fPolygonWidth = 1;
2979 double fPolygonHeight = aB2DRange.getHeight();
2980 if (fPolygonHeight == 0)
2981 fPolygonHeight = 1;
2982 const double aFactorX = static_cast<double>(rMaxRect.GetWidth()) / fPolygonWidth;
2983 const double aFactorY = static_cast<double>(rMaxRect.GetHeight()) / fPolygonHeight;
2985 // Generate matrix, that would produce the desired rMaxRect when applied to unit square
2986 aMathMatrix.scale(aFactorX, aFactorY);
2987 aB2DPolygon = basegfx::utils::createUnitPolygon();
2988 aB2DPolygon.transform(aMathMatrix);
2989 aB2DRange = aB2DPolygon.getB2DRange();
2990 const double fPolygonLeft = aB2DRange.getMinX();
2991 const double fPolygonTop = aB2DRange.getMinY();
2992 aMathMatrix.translate(rMaxRect.Left() - fPolygonLeft, rMaxRect.Top() - fPolygonTop);
2994 // Create a Matrix from aMathMatrix, which is usable with TRSetBaseGeometry
2995 aMathMatrix.decompose(aScale, aTranslate, fRotate, fShearX);
2996 aMatrix = basegfx::utils::createScaleShearXRotateTranslateB2DHomMatrix(
2997 aScale,
2998 basegfx::fTools::equalZero(fShearX) ? 0.0 : -fShearX,
2999 basegfx::fTools::equalZero(fRotate) ? 0.0 : fRotate,
3000 aTranslate);
3002 // Now use TRSetBaseGeometry to actually perform scale, shear, rotate and translate
3003 // on the shape. That considers gluepoints, interaction handles and text area, and includes
3004 // setting rectangles dirty and broadcast.
3005 TRSetBaseGeometry(aMatrix, aPolyPolygon);
3008 void SdrObjCustomShape::TRSetBaseGeometry(const basegfx::B2DHomMatrix& rMatrix, const basegfx::B2DPolyPolygon& /*rPolyPolygon*/)
3010 // The shape might have already flipping in its enhanced geometry. LibreOffice applies
3011 // such after all transformations. We remove it, but remember it to apply them later.
3012 bool bIsMirroredX = IsMirroredX();
3013 bool bIsMirroredY = IsMirroredY();
3014 if (bIsMirroredX || bIsMirroredY)
3016 Point aCurrentCenter = GetSnapRect().Center();
3017 if (bIsMirroredX) // mirror on the y-axis
3019 Mirror(aCurrentCenter, Point(aCurrentCenter.X(), aCurrentCenter.Y() + 1000));
3021 if (bIsMirroredY) // mirror on the x-axis
3023 Mirror(aCurrentCenter, Point(aCurrentCenter.X() + 1000, aCurrentCenter.Y()));
3027 // break up matrix
3028 basegfx::B2DTuple aScale;
3029 basegfx::B2DTuple aTranslate;
3030 double fRotate, fShearX;
3031 rMatrix.decompose(aScale, aTranslate, fRotate, fShearX);
3033 // reset object shear and rotations
3034 m_fObjectRotation = 0.0;
3035 maGeo.m_nRotationAngle = 0_deg100;
3036 maGeo.RecalcSinCos();
3037 maGeo.m_nShearAngle = 0_deg100;
3038 maGeo.RecalcTan();
3040 // if anchor is used, make position relative to it
3041 if(getSdrModelFromSdrObject().IsWriter())
3043 if(GetAnchorPos().X() || GetAnchorPos().Y())
3045 aTranslate += basegfx::B2DTuple(GetAnchorPos().X(), GetAnchorPos().Y());
3049 // scale
3050 Size aSize(basegfx::fround<tools::Long>(fabs(aScale.getX())),
3051 basegfx::fround<tools::Long>(fabs(aScale.getY())));
3052 // fdo#47434 We need a valid rectangle here
3053 if( !aSize.Height() ) aSize.setHeight( 1 );
3054 if( !aSize.Width() ) aSize.setWidth( 1 );
3055 tools::Rectangle aBaseRect(Point(), aSize);
3056 SetLogicRect(aBaseRect);
3058 // Apply flipping from Matrix, which is a transformation relative to origin
3059 if (aScale.getX() < 0.0)
3060 Mirror(Point(0, 0), Point(0, 1000)); // mirror on the y-axis
3061 if (aScale.getY() < 0.0)
3062 Mirror(Point(0, 0), Point(1000, 0)); // mirror on the x-axis
3064 // shear?
3065 if(!basegfx::fTools::equalZero(fShearX))
3067 GeoStat aGeoStat;
3068 // #i123181# The fix for #121932# here was wrong, the trunk version does not correct the
3069 // mirrored shear values, neither at the object level, nor on the API or XML level. Taking
3070 // back the mirroring of the shear angle
3071 aGeoStat.m_nShearAngle = Degree100(basegfx::fround(basegfx::rad2deg<100>(atan(fShearX))));
3072 aGeoStat.RecalcTan();
3073 Shear(Point(), aGeoStat.m_nShearAngle, aGeoStat.mfTanShearAngle, false);
3076 // rotation?
3077 if(!basegfx::fTools::equalZero(fRotate))
3079 GeoStat aGeoStat;
3081 // #i78696#
3082 // fRotate is mathematically correct, but aGeoStat.nRotationAngle is
3083 // mirrored -> mirror value here
3084 aGeoStat.m_nRotationAngle = NormAngle36000(Degree100(basegfx::fround(-basegfx::rad2deg<100>(fRotate))));
3085 aGeoStat.RecalcSinCos();
3086 Rotate(Point(), aGeoStat.m_nRotationAngle, aGeoStat.mfSinRotationAngle, aGeoStat.mfCosRotationAngle);
3089 // translate?
3090 if(!aTranslate.equalZero())
3092 Move(Size(basegfx::fround<tools::Long>(aTranslate.getX()),
3093 basegfx::fround<tools::Long>(aTranslate.getY())));
3096 // Apply flipping from enhanced geometry at center of the shape.
3097 if (!(bIsMirroredX || bIsMirroredY))
3098 return;
3100 // create mathematically matrix for the applied transformations
3101 // aScale was in most cases built from a rectangle including edge
3102 // and is therefore mathematically too large by 1
3103 if (aScale.getX() > 2.0 && aScale.getY() > 2.0)
3104 aScale -= basegfx::B2DTuple(1.0, 1.0);
3105 basegfx::B2DHomMatrix aMathMat = basegfx::utils::createScaleShearXRotateTranslateB2DHomMatrix(
3106 aScale, -fShearX, basegfx::fTools::equalZero(fRotate) ? 0.0 : fRotate,
3107 aTranslate);
3108 // Use matrix to get current center
3109 basegfx::B2DPoint aCenter(0.5,0.5);
3110 aCenter = aMathMat * aCenter;
3111 double fCenterX = aCenter.getX();
3112 double fCenterY = aCenter.getY();
3113 if (bIsMirroredX) // vertical axis
3114 Mirror(Point(basegfx::fround<tools::Long>(fCenterX), basegfx::fround<tools::Long>(fCenterY)),
3115 Point(basegfx::fround<tools::Long>(fCenterX), basegfx::fround<tools::Long>(fCenterY + 1000.0)));
3116 if (bIsMirroredY) // horizontal axis
3117 Mirror(Point(basegfx::fround<tools::Long>(fCenterX), basegfx::fround<tools::Long>(fCenterY)),
3118 Point(basegfx::fround<tools::Long>(fCenterX + 1000.0), basegfx::fround<tools::Long>(fCenterY)));
3121 // taking fObjectRotation instead of aGeo.nAngle
3122 bool SdrObjCustomShape::TRGetBaseGeometry(basegfx::B2DHomMatrix& rMatrix, basegfx::B2DPolyPolygon& /*rPolyPolygon*/) const
3124 // get turn and shear
3125 double fRotate = basegfx::deg2rad(m_fObjectRotation);
3126 double fShearX = toRadians(maGeo.m_nShearAngle);
3128 // get aRectangle, this is the unrotated snaprect
3129 tools::Rectangle aRectangle(getRectangle());
3131 bool bMirroredX = IsMirroredX();
3132 bool bMirroredY = IsMirroredY();
3133 if ( bMirroredX || bMirroredY )
3134 { // we have to retrieve the unmirrored rect
3136 GeoStat aNewGeo(maGeo);
3138 if ( bMirroredX )
3140 fShearX = -fShearX;
3141 tools::Polygon aPol = Rect2Poly(getRectangle(), aNewGeo);
3142 tools::Rectangle aBoundRect( aPol.GetBoundRect() );
3144 Point aRef1( ( aBoundRect.Left() + aBoundRect.Right() ) >> 1, aBoundRect.Top() );
3145 Point aRef2( aRef1.X(), aRef1.Y() + 1000 );
3146 sal_uInt16 i;
3147 sal_uInt16 nPointCount=aPol.GetSize();
3148 for (i=0; i<nPointCount; i++)
3150 MirrorPoint(aPol[i],aRef1,aRef2);
3152 // mirror polygon and move it a bit
3153 tools::Polygon aPol0(aPol);
3154 aPol[0]=aPol0[1];
3155 aPol[1]=aPol0[0];
3156 aPol[2]=aPol0[3];
3157 aPol[3]=aPol0[2];
3158 aPol[4]=aPol0[1];
3159 aRectangle = svx::polygonToRectangle(aPol, aNewGeo);
3161 if ( bMirroredY )
3163 fShearX = -fShearX;
3164 tools::Polygon aPol( Rect2Poly( aRectangle, aNewGeo ) );
3165 tools::Rectangle aBoundRect( aPol.GetBoundRect() );
3167 Point aRef1( aBoundRect.Left(), ( aBoundRect.Top() + aBoundRect.Bottom() ) >> 1 );
3168 Point aRef2( aRef1.X() + 1000, aRef1.Y() );
3169 sal_uInt16 i;
3170 sal_uInt16 nPointCount=aPol.GetSize();
3171 for (i=0; i<nPointCount; i++)
3173 MirrorPoint(aPol[i],aRef1,aRef2);
3175 // mirror polygon and move it a bit
3176 tools::Polygon aPol0(aPol);
3177 aPol[0]=aPol0[1]; // This was WRONG for vertical (!)
3178 aPol[1]=aPol0[0]; // #i121932# Despite my own comment above
3179 aPol[2]=aPol0[3]; // it was *not* wrong even when the reordering
3180 aPol[3]=aPol0[2]; // *seems* to be specific for X-Mirrorings. Oh
3181 aPol[4]=aPol0[1]; // will I be happy when this old stuff is |gone| with aw080 (!)
3182 aRectangle = svx::polygonToRectangle(aPol, aNewGeo);
3186 // fill other values
3187 basegfx::B2DTuple aScale(aRectangle.GetWidth(), aRectangle.GetHeight());
3188 basegfx::B2DTuple aTranslate(aRectangle.Left(), aRectangle.Top());
3190 // position may be relative to anchorpos, convert
3191 if(getSdrModelFromSdrObject().IsWriter())
3193 if(GetAnchorPos().X() || GetAnchorPos().Y())
3195 aTranslate -= basegfx::B2DTuple(GetAnchorPos().X(), GetAnchorPos().Y());
3199 // build matrix
3200 rMatrix = basegfx::utils::createScaleShearXRotateTranslateB2DHomMatrix(
3201 aScale,
3202 basegfx::fTools::equalZero(fShearX) ? 0.0 : tan(fShearX),
3203 basegfx::fTools::equalZero(fRotate) ? 0.0 : -fRotate,
3204 aTranslate);
3206 return false;
3209 std::unique_ptr<sdr::contact::ViewContact> SdrObjCustomShape::CreateObjectSpecificViewContact()
3211 return std::make_unique<sdr::contact::ViewContactOfSdrObjCustomShape>(*this);
3214 // #i33136#
3215 bool SdrObjCustomShape::doConstructOrthogonal(std::u16string_view rName)
3217 bool bRetval(false);
3219 if(o3tl::equalsIgnoreAsciiCase(rName, u"quadrat"))
3221 bRetval = true;
3223 else if(o3tl::equalsIgnoreAsciiCase(rName, u"round-quadrat"))
3225 bRetval = true;
3227 else if(o3tl::equalsIgnoreAsciiCase(rName, u"circle"))
3229 bRetval = true;
3231 else if(o3tl::equalsIgnoreAsciiCase(rName, u"circle-pie"))
3233 bRetval = true;
3235 else if(o3tl::equalsIgnoreAsciiCase(rName, u"ring"))
3237 bRetval = true;
3240 return bRetval;
3243 // #i37011# centralize throw-away of render geometry
3244 void SdrObjCustomShape::InvalidateRenderGeometry()
3246 mXRenderedCustomShape = nullptr;
3247 mpLastShadowGeometry = nullptr;
3250 void SdrObjCustomShape::setUnoShape(const uno::Reference<drawing::XShape>& rxUnoShape)
3252 SdrTextObj::setUnoShape(rxUnoShape);
3254 // The shape engine is created with _current_ shape. This means we
3255 // _must_ reset it when the shape changes.
3256 mxCustomShapeEngine.clear();
3259 OUString SdrObjCustomShape::GetCustomShapeName() const
3261 OUString sShapeName;
3262 OUString aEngine( GetMergedItem( SDRATTR_CUSTOMSHAPE_ENGINE ).GetValue() );
3263 if ( aEngine.isEmpty()
3264 || aEngine == "com.sun.star.drawing.EnhancedCustomShapeEngine" )
3266 OUString sShapeType;
3267 const SdrCustomShapeGeometryItem& rGeometryItem( GetMergedItem( SDRATTR_CUSTOMSHAPE_GEOMETRY ) );
3268 const uno::Any* pAny = rGeometryItem.GetPropertyValueByName( u"Type"_ustr );
3269 if ( pAny && ( *pAny >>= sShapeType ) )
3270 sShapeName = EnhancedCustomShapeTypeNames::GetAccName( sShapeType );
3272 return sShapeName;
3275 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */