1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 /*
3  * This file is part of the LibreOffice project.
4  *
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/.
8  *
9  * This file incorporates work covered by the following license notice:
10  *
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 .
18  */
19 
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>
36 
37 #include <algorithm>
38 
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;
43 
44 namespace oox::drawingml {
45 
46 CustomShapeProperties::CustomShapeProperties()
47 : mnShapePresetType ( -1 )
48 , mbShapeTypeOverride(false)
49 , mbMirroredX   ( false )
50 , mbMirroredY   ( false )
51 , mnTextPreRotateAngle ( 0 )
52 , mnTextCameraZRotateAngle ( 0 )
53 , mnArcNum ( 0 )
54 {
55 }
56 
57 uno::Sequence< sal_Int8 > const & CustomShapeProperties::getShapePresetTypeName() const
58 {
59     return StaticTokenMap().getUtf8TokenName( mnShapePresetType );
60 }
61 
62 sal_Int32 CustomShapeProperties::SetCustomShapeGuideValue( std::vector< CustomShapeGuide >& rGuideList, const CustomShapeGuide& rGuide )
63 {
64     std::vector<CustomShapeGuide>::size_type nIndex = 0;
65     for( ; nIndex < rGuideList.size(); nIndex++ )
66     {
67         if ( rGuideList[ nIndex ].maName == rGuide.maName )
68             break;
69     }
70     if ( nIndex == rGuideList.size() )
71         rGuideList.push_back( rGuide );
72     return static_cast< sal_Int32 >( nIndex );
73 }
74 
75 // returns the index into the guidelist for a given formula name,
76 // if the return value is < 0 then the guide value could not be found
77 sal_Int32 CustomShapeProperties::GetCustomShapeGuideValue( const std::vector< CustomShapeGuide >& rGuideList, std::u16string_view rFormulaName )
78 {
79     // traverse the list from the end, because guide names can be reused
80     // and current is the last one
81     // see a1 guide in gear6 custom shape preset as example
82     sal_Int32 nIndex = static_cast< sal_Int32 >( rGuideList.size() ) - 1;
83     for( ; nIndex >= 0; nIndex-- )
84     {
85         if ( rGuideList[ nIndex ].maName == rFormulaName )
86             break;
87     }
88 
89     return nIndex;
90 }
91 
92 bool CustomShapeProperties::representsDefaultShape() const
93 {
94     return !((getShapePresetType() >= 0 || maPath2DList.size() > 0) &&
95              getShapePresetType() != XML_Rect &&
96              getShapePresetType() != XML_rect);
97 }
98 
99 CustomShapeProperties::PresetDataMap CustomShapeProperties::maPresetDataMap;
100 
101 void CustomShapeProperties::pushToPropSet(
102     const Reference < XPropertySet >& xPropSet, const awt::Size &aSize )
103 {
104     if ( mnShapePresetType >= 0 )
105     {
106         SAL_INFO("oox.drawingml", "preset: " << mnShapePresetType);
107 
108         if (maPresetDataMap.empty())
109             initializePresetDataMap();
110 
111         PropertyMap aPropertyMap;
112         PropertySet aPropSet( xPropSet );
113 
114         if (maPresetDataMap.find(mnShapePresetType) != maPresetDataMap.end())
115         {
116             SAL_INFO(
117                 "oox.drawingml",
118                 "found property map for preset: " << mnShapePresetType);
119 
120             aPropertyMap = maPresetDataMap[mnShapePresetType];
121 #ifdef DEBUG
122             aPropertyMap.dumpCode( aPropertyMap.makePropertySet() );
123 #endif
124         }
125 
126         aPropertyMap.setProperty( PROP_MirroredX, mbMirroredX );
127         aPropertyMap.setProperty( PROP_MirroredY, mbMirroredY );
128         aPropertyMap.setProperty( PROP_TextPreRotateAngle, mnTextPreRotateAngle );
129         aPropertyMap.setProperty( PROP_TextCameraZRotateAngle, mnTextCameraZRotateAngle );
130         if (moTextAreaRotateAngle.has_value())
131             aPropertyMap.setProperty(PROP_TextRotateAngle, moTextAreaRotateAngle.value());
132         Sequence< PropertyValue > aSeq = aPropertyMap.makePropertyValueSequence();
133         aPropSet.setProperty( PROP_CustomShapeGeometry, aSeq );
134 
135         if ( !maAdjustmentGuideList.empty() )
136         {
137             static const OUStringLiteral sCustomShapeGeometry(u"CustomShapeGeometry");
138             static const OUStringLiteral sAdjustmentValues(u"AdjustmentValues");
139             uno::Any aGeoPropSet = xPropSet->getPropertyValue( sCustomShapeGeometry );
140             uno::Sequence< beans::PropertyValue > aGeoPropSeq;
141             if ( aGeoPropSet >>= aGeoPropSeq )
142             {
143                 for ( auto& rGeoProp : asNonConstRange(aGeoPropSeq) )
144                 {
145                     if ( rGeoProp.Name == sAdjustmentValues )
146                     {
147                         uno::Sequence< css::drawing::EnhancedCustomShapeAdjustmentValue > aAdjustmentSeq;
148                         if ( rGeoProp.Value >>= aAdjustmentSeq )
149                         {
150                             auto aAdjustmentSeqRange = asNonConstRange(aAdjustmentSeq);
151                             int nIndex=0;
152                             for (auto const& adjustmentGuide : maAdjustmentGuideList)
153                             {
154                                 if ( adjustmentGuide.maName.getLength() > 3 )
155                                 {
156                                     sal_Int32 nAdjustmentIndex = o3tl::toInt32(adjustmentGuide.maName.subView( 3 )) - 1;
157                                     if ( ( nAdjustmentIndex >= 0 ) && ( nAdjustmentIndex < aAdjustmentSeq.getLength() ) )
158                                     {
159                                         EnhancedCustomShapeAdjustmentValue aAdjustmentVal;
160                                         aAdjustmentVal.Value <<= adjustmentGuide.maFormula.toInt32();
161                                         aAdjustmentVal.State = PropertyState_DIRECT_VALUE;
162                                         aAdjustmentVal.Name = adjustmentGuide.maName;
163                                         aAdjustmentSeqRange[ nAdjustmentIndex ] = aAdjustmentVal;
164                                     }
165                                 } else if ( aAdjustmentSeq.hasElements() ) {
166                                     EnhancedCustomShapeAdjustmentValue aAdjustmentVal;
167                                     aAdjustmentVal.Value <<= adjustmentGuide.maFormula.toInt32();
168                                     aAdjustmentVal.State = PropertyState_DIRECT_VALUE;
169                                     aAdjustmentVal.Name = adjustmentGuide.maName;
170                                     if (nIndex < aAdjustmentSeq.getLength())
171                                     {
172                                         aAdjustmentSeqRange[nIndex] = aAdjustmentVal;
173                                         ++nIndex;
174                                     }
175                                 }
176                             }
177                             rGeoProp.Value <<= aAdjustmentSeq;
178                             xPropSet->setPropertyValue( sCustomShapeGeometry, Any( aGeoPropSeq ) );
179                             break;
180                         }
181                     }
182                 }
183             }
184         }
185     }
186     else
187     {
188         PropertyMap aPropertyMap;
189         aPropertyMap.setProperty( PROP_Type, OUString( "ooxml-non-primitive" ));
190         aPropertyMap.setProperty( PROP_MirroredX, mbMirroredX );
191         aPropertyMap.setProperty( PROP_MirroredY, mbMirroredY );
192         if( mnTextPreRotateAngle )
193             aPropertyMap.setProperty( PROP_TextPreRotateAngle, mnTextPreRotateAngle );
194         if (moTextAreaRotateAngle.has_value())
195             aPropertyMap.setProperty(PROP_TextRotateAngle, moTextAreaRotateAngle.value());
196         // Note 1: If Equations are defined - they are processed using internal div by 360 coordinates
197         // while if they are not, standard ooxml coordinates are used.
198         // This size specifically affects scaling.
199         // Note 2: Width and Height are set to 0 to force scaling to 1.
200         awt::Rectangle aViewBox( 0, 0, aSize.Width, aSize.Height );
201         if( !maGuideList.empty() )
202             aViewBox = awt::Rectangle( 0, 0, 0, 0 );
203         aPropertyMap.setProperty( PROP_ViewBox, aViewBox);
204 
205         Sequence< EnhancedCustomShapeAdjustmentValue > aAdjustmentValues( maAdjustmentGuideList.size() );
206         auto aAdjustmentValuesRange = asNonConstRange(aAdjustmentValues);
207         for ( std::vector<CustomShapeGuide>::size_type i = 0; i < maAdjustmentGuideList.size(); i++ )
208         {
209             EnhancedCustomShapeAdjustmentValue aAdjustmentVal;
210             aAdjustmentVal.Value <<= maAdjustmentGuideList[ i ].maFormula.toInt32();
211             aAdjustmentVal.State = PropertyState_DIRECT_VALUE;
212             aAdjustmentVal.Name = maAdjustmentGuideList[ i ].maName;
213             aAdjustmentValuesRange[ i ] = aAdjustmentVal;
214         }
215         aPropertyMap.setProperty( PROP_AdjustmentValues, aAdjustmentValues);
216 
217         PropertyMap aPath;
218 
219         aPath.setProperty( PROP_Segments, comphelper::containerToSequence(maSegments) );
220 
221         if ( maTextRect.has_value() ) {
222             Sequence< EnhancedCustomShapeTextFrame > aTextFrames{
223                 { /* tl */ { maTextRect.value().l, maTextRect.value().t },
224                   /* br */ { maTextRect.value().r, maTextRect.value().b } }
225             };
226             aPath.setProperty( PROP_TextFrames, aTextFrames);
227         }
228 
229         sal_uInt32 nParameterPairs = 0;
230         for ( auto const & i: maPath2DList )
231             nParameterPairs += i.parameter.size();
232 
233         Sequence< EnhancedCustomShapeParameterPair > aParameterPairs( nParameterPairs );
234         auto aParameterPairsRange = asNonConstRange(aParameterPairs);
235         sal_uInt32 k = 0;
236         for ( auto const & i: maPath2DList )
237             for ( auto const & j: i.parameter )
238                 aParameterPairsRange[ k++ ] = j;
239         aPath.setProperty( PROP_Coordinates, aParameterPairs);
240 
241         if ( !maPath2DList.empty() )
242         {
243             bool bAllZero = true;
244             for ( auto const & i: maPath2DList )
245             {
246                 if ( i.w || i.h ) {
247                     bAllZero = false;
248                     break;
249                 }
250             }
251 
252             if ( !bAllZero ) {
253                 Sequence< awt::Size > aSubViewSize( maPath2DList.size() );
254                 std::transform(maPath2DList.begin(), maPath2DList.end(), aSubViewSize.getArray(),
255                                [](const auto& p2d)
256                                {
257                                    SAL_INFO("oox.cscode",
258                                             "set subpath; size: " << p2d.w << " x " << p2d.h);
259                                    return awt::Size(p2d.w, p2d.h);
260                                });
261                 aPath.setProperty( PROP_SubViewSize, aSubViewSize);
262             }
263         }
264 
265         Sequence< PropertyValue > aPathSequence = aPath.makePropertyValueSequence();
266         aPropertyMap.setProperty( PROP_Path, aPathSequence);
267 
268         Sequence< OUString > aEquations( maGuideList.size() );
269         std::transform(maGuideList.begin(), maGuideList.end(), aEquations.getArray(),
270                        [](const auto& g) { return g.maFormula; });
271         aPropertyMap.setProperty( PROP_Equations, aEquations);
272 
273         Sequence< PropertyValues > aHandles( maAdjustHandleList.size() );
274         auto aHandlesRange = asNonConstRange(aHandles);
275         for ( std::vector<AdjustHandle>::size_type i = 0; i < maAdjustHandleList.size(); i++ )
276         {
277             PropertyMap aHandle;
278             // maAdjustmentHandle[ i ].gdRef1 ... maAdjustmentHandle[ i ].gdRef2 ... :(
279             // gdRef1 && gdRef2 -> we do not offer such reference, so it is difficult
280             // to determine the correct adjustment handle that should be updated with the adjustment
281             // position. here is the solution: the adjustment value that is used within the position
282             // has to be updated, in case the position is a formula the first usage of a
283             // adjustment value is decisive
284             if ( maAdjustHandleList[ i ].polar )
285             {
286                 // Polar handles in DrawingML
287                 // 1. don't have reference center, so PROP_Polar isn't needed.
288                 // 2. position always use planar coordinates.
289                 // 3. use RefAngle and RefR to specify adjustment value to be updated.
290                 // 4. The unit of angular adjustment values are 6000th degree.
291 
292                 aHandle.setProperty( PROP_Position, maAdjustHandleList[ i ].pos);
293                 if ( maAdjustHandleList[ i ].gdRef1.has_value() )
294                 {
295                     sal_Int32 nIndex = GetCustomShapeGuideValue( maAdjustmentGuideList, maAdjustHandleList[ i ].gdRef1.value() );
296                     if ( nIndex >= 0 )
297                         aHandle.setProperty( PROP_RefR, nIndex);
298                 }
299                 if ( maAdjustHandleList[ i ].gdRef2.has_value() )
300                 {
301                     sal_Int32 nIndex = GetCustomShapeGuideValue( maAdjustmentGuideList, maAdjustHandleList[ i ].gdRef2.value() );
302                     if ( nIndex >= 0 )
303                         aHandle.setProperty( PROP_RefAngle, nIndex);
304                 }
305                 if ( maAdjustHandleList[ i ].min1.has_value() )
306                     aHandle.setProperty( PROP_RadiusRangeMinimum, maAdjustHandleList[ i ].min1.value());
307                 if ( maAdjustHandleList[ i ].max1.has_value() )
308                     aHandle.setProperty( PROP_RadiusRangeMaximum, maAdjustHandleList[ i ].max1.value());
309 
310                 /* TODO: AngleMin & AngleMax
311                 if ( maAdjustHandleList[ i ].min2.has() )
312                     aHandle.setProperty( PROP_ ] = maAdjustHandleList[ i ].min2.get());
313                 if ( maAdjustHandleList[ i ].max2.has() )
314                     aHandle.setProperty( PROP_ ] = maAdjustHandleList[ i ].max2.get());
315                 */
316             }
317             else
318             {
319                 aHandle.setProperty( PROP_Position, maAdjustHandleList[ i ].pos);
320                 if ( maAdjustHandleList[ i ].gdRef1.has_value() )
321                 {
322                     // TODO: PROP_RefX and PROP_RefY are not yet part of our file format,
323                     // so the handles will not work after save/reload
324                     sal_Int32 nIndex = GetCustomShapeGuideValue( maAdjustmentGuideList, maAdjustHandleList[ i ].gdRef1.value() );
325                     if ( nIndex >= 0 )
326                         aHandle.setProperty( PROP_RefX, nIndex);
327                 }
328                 if ( maAdjustHandleList[ i ].gdRef2.has_value() )
329                 {
330                     sal_Int32 nIndex = GetCustomShapeGuideValue( maAdjustmentGuideList, maAdjustHandleList[ i ].gdRef2.value() );
331                     if ( nIndex >= 0 )
332                         aHandle.setProperty( PROP_RefY, nIndex);
333                 }
334                 if ( maAdjustHandleList[ i ].min1.has_value() )
335                     aHandle.setProperty( PROP_RangeXMinimum, maAdjustHandleList[ i ].min1.value());
336                 if ( maAdjustHandleList[ i ].max1.has_value() )
337                     aHandle.setProperty( PROP_RangeXMaximum, maAdjustHandleList[ i ].max1.value());
338                 if ( maAdjustHandleList[ i ].min2.has_value() )
339                     aHandle.setProperty( PROP_RangeYMinimum, maAdjustHandleList[ i ].min2.value());
340                 if ( maAdjustHandleList[ i ].max2.has_value() )
341                     aHandle.setProperty( PROP_RangeYMaximum, maAdjustHandleList[ i ].max2.value());
342             }
343             aHandlesRange[ i ] = aHandle.makePropertyValueSequence();
344         }
345         aPropertyMap.setProperty( PROP_Handles, aHandles);
346 
347 #ifdef DEBUG
348         // Note that the script oox/source/drawingml/customshapes/generatePresetsData.pl looks
349         // for these ==cscode== and ==csdata== markers, so don't "clean up" these SAL_INFOs.
350         SAL_INFO("oox.cscode", "==cscode== begin");
351         aPropertyMap.dumpCode( aPropertyMap.makePropertySet() );
352         SAL_INFO("oox.cscode", "==cscode== end");
353         SAL_INFO("oox.csdata", "==csdata== begin");
354         aPropertyMap.dumpData( aPropertyMap.makePropertySet() );
355         SAL_INFO("oox.csdata", "==csdata== end");
356 #endif
357         // converting the vector to a sequence
358         Sequence< PropertyValue > aSeq = aPropertyMap.makePropertyValueSequence();
359         PropertySet aPropSet( xPropSet );
360         aPropSet.setProperty( PROP_CustomShapeGeometry, aSeq );
361     }
362 }
363 
364 }
365 
366 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */
367