1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
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 <drawingml/customshapeproperties.hxx>
21 #include <oox/helper/propertymap.hxx>
22 #include <oox/helper/propertyset.hxx>
23 #include <oox/token/properties.hxx>
24 #include <oox/token/tokenmap.hxx>
25 #include <com/sun/star/awt/Rectangle.hpp>
26 #include <com/sun/star/awt/Size.hpp>
27 #include <com/sun/star/beans/PropertyValues.hpp>
28 #include <com/sun/star/beans/XPropertySet.hpp>
29 #include <com/sun/star/drawing/EnhancedCustomShapeAdjustmentValue.hpp>
30 #include <com/sun/star/drawing/EnhancedCustomShapeTextFrame.hpp>
31 #include <com/sun/star/drawing/XEnhancedCustomShapeDefaulter.hpp>
32 #include <com/sun/star/drawing/XShape.hpp>
33 #include <comphelper/sequence.hxx>
34 #include <o3tl/string_view.hxx>
35 #include <sal/log.hxx>
39 using namespace ::com::sun::star
;
40 using namespace ::com::sun::star::uno
;
41 using namespace ::com::sun::star::beans
;
42 using namespace ::com::sun::star::drawing
;
44 namespace oox::drawingml
{
46 void CustomShapeGuideContainer::ActualizeLookupMap() const
48 if ( mbLookupMapStale
)
50 // maGuideListLookupMap maps guide name to index in maGuideList
51 // guides were added since last actualization, need to update map based on those
52 // guide names can be reused, and current is the latest one
53 // (see a1 guide in gear6 custom shape preset as example):
54 // go backwards and update if index is higher than previously
55 for ( sal_Int32 nIndex
= static_cast<sal_Int32
>( maGuideList
.size() ) - 1; nIndex
>= mnPreviousActSize
; --nIndex
)
57 const auto it
= maGuideListLookupMap
.find( maGuideList
[ nIndex
].maName
);
58 if ( it
!= maGuideListLookupMap
.end() )
60 if ( nIndex
> it
->second
)
64 maGuideListLookupMap
[ maGuideList
[ nIndex
].maName
] = nIndex
;
66 mbLookupMapStale
= false;
67 mnPreviousActSize
= static_cast<sal_Int32
>( maGuideList
.size() );
71 void CustomShapeGuideContainer::push_back( const CustomShapeGuide
& rGuide
)
73 if ( !mbLookupMapStale
)
75 mbLookupMapStale
= true;
76 mnPreviousActSize
= static_cast<sal_Int32
>( maGuideList
.size() );
78 maGuideList
.push_back( rGuide
);
81 // returns the index into the guidelist for a given formula name,
82 // if the return value is < 0 then the guide value could not be found
83 sal_Int32
CustomShapeGuideContainer::GetCustomShapeGuideValue( const OUString
&rFormulaName
) const
86 const auto it
= maGuideListLookupMap
.find( rFormulaName
);
87 if ( it
!= maGuideListLookupMap
.end() )
93 sal_Int32
CustomShapeGuideContainer::SetCustomShapeGuideValue( const CustomShapeGuide
& rGuide
)
96 // change from previous SetCustomShapeGuideValue behavior: searching using cache traverses backwards
97 const auto it
= maGuideListLookupMap
.find( rGuide
.maName
);
98 if ( it
!= maGuideListLookupMap
.end() )
101 maGuideList
.push_back( rGuide
);
102 maGuideListLookupMap
[ rGuide
.maName
] = mnPreviousActSize
;
104 return mnPreviousActSize
- 1;
107 CustomShapeProperties::CustomShapeProperties()
108 : mnShapePresetType ( -1 )
109 , mbShapeTypeOverride(false)
110 , mbMirroredX ( false )
111 , mbMirroredY ( false )
112 , mnTextPreRotateAngle ( 0 )
113 , mnTextCameraZRotateAngle ( 0 )
118 OUString
CustomShapeProperties::getShapePresetTypeName() const
120 return TokenMap::getUnicodeTokenName(mnShapePresetType
);
123 bool CustomShapeProperties::representsDefaultShape() const
125 return !((getShapePresetType() >= 0 || maPath2DList
.size() > 0) &&
126 getShapePresetType() != XML_Rect
&&
127 getShapePresetType() != XML_rect
);
130 CustomShapeProperties::PresetDataMap
CustomShapeProperties::maPresetDataMap
;
132 void CustomShapeProperties::pushToPropSet(
133 const Reference
< XPropertySet
>& xPropSet
, const awt::Size
&aSize
)
135 if ( mnShapePresetType
>= 0 )
137 SAL_INFO("oox.drawingml", "preset: " << mnShapePresetType
);
139 if (maPresetDataMap
.empty())
140 initializePresetDataMap();
142 PropertyMap aPropertyMap
;
143 PropertySet
aPropSet( xPropSet
);
145 if (maPresetDataMap
.contains(mnShapePresetType
))
149 "found property map for preset: " << mnShapePresetType
);
151 aPropertyMap
= maPresetDataMap
[mnShapePresetType
];
152 #if OSL_DEBUG_LEVEL >= 2
153 aPropertyMap
.dumpCode( aPropertyMap
.makePropertySet() );
157 aPropertyMap
.setProperty( PROP_MirroredX
, mbMirroredX
);
158 aPropertyMap
.setProperty( PROP_MirroredY
, mbMirroredY
);
159 aPropertyMap
.setProperty( PROP_TextPreRotateAngle
, mnTextPreRotateAngle
);
160 aPropertyMap
.setProperty( PROP_TextCameraZRotateAngle
, mnTextCameraZRotateAngle
);
161 if (moTextAreaRotateAngle
.has_value())
162 aPropertyMap
.setProperty(PROP_TextRotateAngle
, moTextAreaRotateAngle
.value());
163 if (!maExtrusionPropertyMap
.empty())
165 Sequence
< PropertyValue
> aExtrusionSequence
= maExtrusionPropertyMap
.makePropertyValueSequence();
166 aPropertyMap
.setAnyProperty( PROP_Extrusion
, css::uno::Any(aExtrusionSequence
));
168 Sequence
< PropertyValue
> aSeq
= aPropertyMap
.makePropertyValueSequence();
169 aPropSet
.setProperty( PROP_CustomShapeGeometry
, aSeq
);
171 if ( !maAdjustmentGuideList
.empty() )
173 static constexpr OUString
sCustomShapeGeometry(u
"CustomShapeGeometry"_ustr
);
174 static constexpr OUStringLiteral
sAdjustmentValues(u
"AdjustmentValues");
175 uno::Any aGeoPropSet
= xPropSet
->getPropertyValue( sCustomShapeGeometry
);
176 uno::Sequence
< beans::PropertyValue
> aGeoPropSeq
;
177 if ( aGeoPropSet
>>= aGeoPropSeq
)
179 for ( auto& rGeoProp
: asNonConstRange(aGeoPropSeq
) )
181 if ( rGeoProp
.Name
== sAdjustmentValues
)
183 uno::Sequence
< css::drawing::EnhancedCustomShapeAdjustmentValue
> aAdjustmentSeq
;
184 if ( rGeoProp
.Value
>>= aAdjustmentSeq
)
186 auto aAdjustmentSeqRange
= asNonConstRange(aAdjustmentSeq
);
188 for (auto const& adjustmentGuide
: maAdjustmentGuideList
)
190 if ( adjustmentGuide
.maName
.getLength() > 3 )
192 sal_Int32 nAdjustmentIndex
= o3tl::toInt32(adjustmentGuide
.maName
.subView( 3 )) - 1;
193 if ( ( nAdjustmentIndex
>= 0 ) && ( nAdjustmentIndex
< aAdjustmentSeq
.getLength() ) )
195 EnhancedCustomShapeAdjustmentValue aAdjustmentVal
;
196 aAdjustmentVal
.Value
<<= adjustmentGuide
.maFormula
.toInt32();
197 aAdjustmentVal
.State
= PropertyState_DIRECT_VALUE
;
198 aAdjustmentVal
.Name
= adjustmentGuide
.maName
;
199 aAdjustmentSeqRange
[ nAdjustmentIndex
] = std::move(aAdjustmentVal
);
201 } else if ( aAdjustmentSeq
.hasElements() ) {
202 EnhancedCustomShapeAdjustmentValue aAdjustmentVal
;
203 aAdjustmentVal
.Value
<<= adjustmentGuide
.maFormula
.toInt32();
204 aAdjustmentVal
.State
= PropertyState_DIRECT_VALUE
;
205 aAdjustmentVal
.Name
= adjustmentGuide
.maName
;
206 if (nIndex
< aAdjustmentSeq
.getLength())
208 aAdjustmentSeqRange
[nIndex
] = std::move(aAdjustmentVal
);
213 rGeoProp
.Value
<<= aAdjustmentSeq
;
214 xPropSet
->setPropertyValue( sCustomShapeGeometry
, Any( aGeoPropSeq
) );
224 PropertyMap aPropertyMap
;
225 aPropertyMap
.setProperty( PROP_Type
, u
"ooxml-non-primitive"_ustr
);
226 aPropertyMap
.setProperty( PROP_MirroredX
, mbMirroredX
);
227 aPropertyMap
.setProperty( PROP_MirroredY
, mbMirroredY
);
228 if( mnTextPreRotateAngle
)
229 aPropertyMap
.setProperty( PROP_TextPreRotateAngle
, mnTextPreRotateAngle
);
230 if (moTextAreaRotateAngle
.has_value())
231 aPropertyMap
.setProperty(PROP_TextRotateAngle
, moTextAreaRotateAngle
.value());
232 // Note 1: If Equations are defined - they are processed using internal div by 360 coordinates
233 // while if they are not, standard ooxml coordinates are used.
234 // This size specifically affects scaling.
235 // Note 2: Width and Height are set to 0 to force scaling to 1.
236 awt::Rectangle
aViewBox( 0, 0, aSize
.Width
, aSize
.Height
);
237 // tdf#113187 Each ArcTo introduces two additional equations for converting start and swing
238 // angles. They do not count for test for use of standard ooxml coordinates
239 if (maGuideList
.size() - 2 * countArcTo() > 0)
240 aViewBox
= awt::Rectangle( 0, 0, 0, 0 );
241 aPropertyMap
.setProperty( PROP_ViewBox
, aViewBox
);
243 Sequence
< EnhancedCustomShapeAdjustmentValue
> aAdjustmentValues( maAdjustmentGuideList
.size() );
244 auto aAdjustmentValuesRange
= asNonConstRange(aAdjustmentValues
);
245 for ( std::vector
<CustomShapeGuide
>::size_type i
= 0; i
< maAdjustmentGuideList
.size(); i
++ )
247 EnhancedCustomShapeAdjustmentValue aAdjustmentVal
;
248 aAdjustmentVal
.Value
<<= maAdjustmentGuideList
[ i
].maFormula
.toInt32();
249 aAdjustmentVal
.State
= PropertyState_DIRECT_VALUE
;
250 aAdjustmentVal
.Name
= maAdjustmentGuideList
[ i
].maName
;
251 aAdjustmentValuesRange
[i
] = std::move(aAdjustmentVal
);
253 aPropertyMap
.setProperty( PROP_AdjustmentValues
, aAdjustmentValues
);
257 aPath
.setProperty( PROP_Segments
, comphelper::containerToSequence(maSegments
) );
259 if ( maTextRect
.has_value() ) {
260 Sequence
< EnhancedCustomShapeTextFrame
> aTextFrames
{
261 { /* tl */ { maTextRect
.value().l
, maTextRect
.value().t
},
262 /* br */ { maTextRect
.value().r
, maTextRect
.value().b
} }
264 aPath
.setProperty( PROP_TextFrames
, aTextFrames
);
267 if (!maConnectionSiteList
.empty())
269 css::uno::Sequence
<EnhancedCustomShapeParameterPair
> seqGluePoints
;
270 seqGluePoints
.realloc(maConnectionSiteList
.size());
272 for (auto& rGluePoint
: asNonConstRange(seqGluePoints
))
274 rGluePoint
.First
.Value
= maConnectionSiteList
[nId
].pos
.First
.Value
;
275 rGluePoint
.First
.Type
= maConnectionSiteList
[nId
].pos
.First
.Type
;
276 rGluePoint
.Second
.Value
= maConnectionSiteList
[nId
].pos
.Second
.Value
;
277 rGluePoint
.Second
.Type
= maConnectionSiteList
[nId
].pos
.Second
.Type
;
280 aPath
.setProperty(PROP_GluePoints
, seqGluePoints
);
283 sal_uInt32 nParameterPairs
= 0;
284 for ( auto const & i
: maPath2DList
)
285 nParameterPairs
+= i
.parameter
.size();
287 Sequence
< EnhancedCustomShapeParameterPair
> aParameterPairs( nParameterPairs
);
288 auto aParameterPairsRange
= asNonConstRange(aParameterPairs
);
290 for ( auto const & i
: maPath2DList
)
291 for ( auto const & j
: i
.parameter
)
292 aParameterPairsRange
[ k
++ ] = j
;
293 aPath
.setProperty( PROP_Coordinates
, aParameterPairs
);
295 if ( !maPath2DList
.empty() )
297 bool bAllZero
= true;
298 for ( auto const & i
: maPath2DList
)
307 Sequence
< awt::Size
> aSubViewSize( maPath2DList
.size() );
308 std::transform(maPath2DList
.begin(), maPath2DList
.end(), aSubViewSize
.getArray(),
311 SAL_INFO("oox.cscode",
312 "set subpath; size: " << p2d
.w
<< " x " << p2d
.h
);
313 return awt::Size(p2d
.w
, p2d
.h
);
315 aPath
.setProperty( PROP_SubViewSize
, aSubViewSize
);
319 Sequence
< PropertyValue
> aPathSequence
= aPath
.makePropertyValueSequence();
320 aPropertyMap
.setProperty( PROP_Path
, aPathSequence
);
322 Sequence
< OUString
> aEquations( maGuideList
.size() );
323 std::transform(maGuideList
.begin(), maGuideList
.end(), aEquations
.getArray(),
324 [](const auto& g
) { return g
.maFormula
; });
325 aPropertyMap
.setProperty( PROP_Equations
, aEquations
);
327 Sequence
< PropertyValues
> aHandles( maAdjustHandleList
.size() );
328 auto aHandlesRange
= asNonConstRange(aHandles
);
329 for ( std::vector
<AdjustHandle
>::size_type i
= 0; i
< maAdjustHandleList
.size(); i
++ )
332 // maAdjustmentHandle[ i ].gdRef1 ... maAdjustmentHandle[ i ].gdRef2 ... :(
333 // gdRef1 && gdRef2 -> we do not offer such reference, so it is difficult
334 // to determine the correct adjustment handle that should be updated with the adjustment
335 // position. here is the solution: the adjustment value that is used within the position
336 // has to be updated, in case the position is a formula the first usage of a
337 // adjustment value is decisive
338 if ( maAdjustHandleList
[ i
].polar
)
340 // Polar handles in DrawingML
341 // 1. don't have reference center, so PROP_Polar isn't needed.
342 // 2. position always use planar coordinates.
343 // 3. use RefAngle and RefR to specify adjustment value to be updated.
344 // 4. The unit of angular adjustment values are 6000th degree.
346 aHandle
.setProperty( PROP_Position
, maAdjustHandleList
[ i
].pos
);
347 if ( maAdjustHandleList
[ i
].gdRef1
.has_value() )
349 sal_Int32 nIndex
= maAdjustmentGuideList
.GetCustomShapeGuideValue( maAdjustHandleList
[ i
].gdRef1
.value() );
351 aHandle
.setProperty( PROP_RefR
, nIndex
);
353 if ( maAdjustHandleList
[ i
].gdRef2
.has_value() )
355 sal_Int32 nIndex
= maAdjustmentGuideList
.GetCustomShapeGuideValue( maAdjustHandleList
[ i
].gdRef2
.value() );
357 aHandle
.setProperty( PROP_RefAngle
, nIndex
);
359 if ( maAdjustHandleList
[ i
].min1
.has_value() )
360 aHandle
.setProperty( PROP_RadiusRangeMinimum
, maAdjustHandleList
[ i
].min1
.value());
361 if ( maAdjustHandleList
[ i
].max1
.has_value() )
362 aHandle
.setProperty( PROP_RadiusRangeMaximum
, maAdjustHandleList
[ i
].max1
.value());
364 /* TODO: AngleMin & AngleMax
365 if ( maAdjustHandleList[ i ].min2.has() )
366 aHandle.setProperty( PROP_ ] = maAdjustHandleList[ i ].min2.get());
367 if ( maAdjustHandleList[ i ].max2.has() )
368 aHandle.setProperty( PROP_ ] = maAdjustHandleList[ i ].max2.get());
373 aHandle
.setProperty( PROP_Position
, maAdjustHandleList
[ i
].pos
);
374 if ( maAdjustHandleList
[ i
].gdRef1
.has_value() )
376 // TODO: PROP_RefX and PROP_RefY are not yet part of our file format,
377 // so the handles will not work after save/reload
378 sal_Int32 nIndex
= maAdjustmentGuideList
.GetCustomShapeGuideValue( maAdjustHandleList
[ i
].gdRef1
.value() );
380 aHandle
.setProperty( PROP_RefX
, nIndex
);
382 if ( maAdjustHandleList
[ i
].gdRef2
.has_value() )
384 sal_Int32 nIndex
= maAdjustmentGuideList
.GetCustomShapeGuideValue( maAdjustHandleList
[ i
].gdRef2
.value() );
386 aHandle
.setProperty( PROP_RefY
, nIndex
);
388 if ( maAdjustHandleList
[ i
].min1
.has_value() )
389 aHandle
.setProperty( PROP_RangeXMinimum
, maAdjustHandleList
[ i
].min1
.value());
390 if ( maAdjustHandleList
[ i
].max1
.has_value() )
391 aHandle
.setProperty( PROP_RangeXMaximum
, maAdjustHandleList
[ i
].max1
.value());
392 if ( maAdjustHandleList
[ i
].min2
.has_value() )
393 aHandle
.setProperty( PROP_RangeYMinimum
, maAdjustHandleList
[ i
].min2
.value());
394 if ( maAdjustHandleList
[ i
].max2
.has_value() )
395 aHandle
.setProperty( PROP_RangeYMaximum
, maAdjustHandleList
[ i
].max2
.value());
397 aHandlesRange
[ i
] = aHandle
.makePropertyValueSequence();
399 aPropertyMap
.setProperty( PROP_Handles
, aHandles
);
400 if (!maExtrusionPropertyMap
.empty())
402 Sequence
< PropertyValue
> aExtrusionSequence
= maExtrusionPropertyMap
.makePropertyValueSequence();
403 aPropertyMap
.setProperty( PROP_Extrusion
, aExtrusionSequence
);
406 #if OSL_DEBUG_LEVEL >= 2
407 // Note that the script oox/source/drawingml/customshapes/generatePresetsData.pl looks
408 // for these ==cscode== and ==csdata== markers, so don't "clean up" these SAL_INFOs.
409 SAL_INFO("oox.cscode", "==cscode== begin");
410 aPropertyMap
.dumpCode( aPropertyMap
.makePropertySet() );
411 SAL_INFO("oox.cscode", "==cscode== end");
412 SAL_INFO("oox.csdata", "==csdata== begin");
413 aPropertyMap
.dumpData( aPropertyMap
.makePropertySet() );
414 SAL_INFO("oox.csdata", "==csdata== end");
416 // converting the vector to a sequence
417 Sequence
< PropertyValue
> aSeq
= aPropertyMap
.makePropertyValueSequence();
418 PropertySet
aPropSet( xPropSet
);
419 aPropSet
.setProperty( PROP_CustomShapeGeometry
, aSeq
);
425 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */