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
