1 /************************************************************************* 2 * 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4 * 5 * Copyright 2000, 2010 Oracle and/or its affiliates. 6 * 7 * OpenOffice.org - a multi-platform office productivity suite 8 * 9 * This file is part of OpenOffice.org. 10 * 11 * OpenOffice.org is free software: you can redistribute it and/or modify 12 * it under the terms of the GNU Lesser General Public License version 3 13 * only, as published by the Free Software Foundation. 14 * 15 * OpenOffice.org is distributed in the hope that it will be useful, 16 * but WITHOUT ANY WARRANTY; without even the implied warranty of 17 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 18 * GNU Lesser General Public License version 3 for more details 19 * (a copy is included in the LICENSE file that accompanied this code). 20 * 21 * You should have received a copy of the GNU Lesser General Public License 22 * version 3 along with OpenOffice.org. If not, see 23 * <http://www.openoffice.org/license.html> 24 * for a copy of the LGPLv3 License. 25 * 26 ************************************************************************/ 27 28 #include "oox/drawingml/lineproperties.hxx" 29 #include <vector> 30 #include <rtl/ustrbuf.hxx> 31 #include <com/sun/star/beans/NamedValue.hpp> 32 #include <com/sun/star/container/XNameContainer.hpp> 33 #include <com/sun/star/drawing/FlagSequence.hpp> 34 #include <com/sun/star/drawing/LineDash.hpp> 35 #include <com/sun/star/drawing/LineJoint.hpp> 36 #include <com/sun/star/drawing/LineStyle.hpp> 37 #include <com/sun/star/drawing/PointSequence.hpp> 38 #include <com/sun/star/drawing/PolyPolygonBezierCoords.hpp> 39 #include "oox/drawingml/drawingmltypes.hxx" 40 #include "oox/drawingml/shapepropertymap.hxx" 41 #include "oox/helper/containerhelper.hxx" 42 #include "oox/helper/graphichelper.hxx" 43 #include "oox/token/tokens.hxx" 44 45 using namespace ::com::sun::star::beans; 46 using namespace ::com::sun::star::drawing; 47 48 using ::rtl::OUString; 49 using ::rtl::OUStringBuffer; 50 using ::com::sun::star::uno::Any; 51 using ::com::sun::star::uno::Reference; 52 using ::com::sun::star::awt::Point; 53 using ::com::sun::star::container::XNameContainer; 54 55 namespace oox { 56 namespace drawingml { 57 58 // ============================================================================ 59 60 namespace { 61 62 void lclSetDashData( LineDash& orLineDash, sal_Int16 nDots, sal_Int32 nDotLen, 63 sal_Int16 nDashes, sal_Int32 nDashLen, sal_Int32 nDistance ) 64 { 65 orLineDash.Dots = nDots; 66 orLineDash.DotLen = nDotLen; 67 orLineDash.Dashes = nDashes; 68 orLineDash.DashLen = nDashLen; 69 orLineDash.Distance = nDistance; 70 } 71 72 /** Converts the specified preset dash to API dash. 73 74 Line length and dot length are set relative to line width and have to be 75 multiplied by the actual line width after this function. 76 */ 77 void lclConvertPresetDash( LineDash& orLineDash, sal_Int32 nPresetDash ) 78 { 79 switch( nPresetDash ) 80 { 81 case XML_dot: lclSetDashData( orLineDash, 1, 1, 0, 0, 3 ); break; 82 case XML_dash: lclSetDashData( orLineDash, 0, 0, 1, 4, 3 ); break; 83 case XML_dashDot: lclSetDashData( orLineDash, 1, 1, 1, 4, 3 ); break; 84 85 case XML_lgDash: lclSetDashData( orLineDash, 0, 0, 1, 8, 3 ); break; 86 case XML_lgDashDot: lclSetDashData( orLineDash, 1, 1, 1, 8, 3 ); break; 87 case XML_lgDashDotDot: lclSetDashData( orLineDash, 2, 1, 1, 8, 3 ); break; 88 89 case XML_sysDot: lclSetDashData( orLineDash, 1, 1, 0, 0, 1 ); break; 90 case XML_sysDash: lclSetDashData( orLineDash, 0, 0, 1, 3, 1 ); break; 91 case XML_sysDashDot: lclSetDashData( orLineDash, 1, 1, 1, 3, 1 ); break; 92 case XML_sysDashDotDot: lclSetDashData( orLineDash, 2, 1, 1, 3, 1 ); break; 93 94 default: 95 OSL_ENSURE( false, "lclConvertPresetDash - unsupported preset dash" ); 96 lclSetDashData( orLineDash, 0, 0, 1, 4, 3 ); 97 } 98 } 99 100 /** Converts the passed custom dash to API dash. 101 102 Line length and dot length are set relative to line width and have to be 103 multiplied by the actual line width after this function. 104 */ 105 void lclConvertCustomDash( LineDash& orLineDash, const LineProperties::DashStopVector& rCustomDash ) 106 { 107 if( rCustomDash.empty() ) 108 { 109 OSL_ENSURE( false, "lclConvertCustomDash - unexpected empty custom dash" ); 110 lclSetDashData( orLineDash, 0, 0, 1, 4, 3 ); 111 return; 112 } 113 114 // count dashes and dots (stops equal or less than 2 are assumed to be dots) 115 sal_Int16 nDots = 0; 116 sal_Int32 nDotLen = 0; 117 sal_Int16 nDashes = 0; 118 sal_Int32 nDashLen = 0; 119 sal_Int32 nDistance = 0; 120 for( LineProperties::DashStopVector::const_iterator aIt = rCustomDash.begin(), aEnd = rCustomDash.end(); aIt != aEnd; ++aIt ) 121 { 122 if( aIt->first <= 2 ) 123 { 124 ++nDots; 125 nDotLen += aIt->first; 126 } 127 else 128 { 129 ++nDashes; 130 nDashLen += aIt->first; 131 } 132 nDistance += aIt->second; 133 } 134 orLineDash.DotLen = (nDots > 0) ? ::std::max< sal_Int32 >( nDotLen / nDots, 1 ) : 0; 135 orLineDash.Dots = nDots; 136 orLineDash.DashLen = (nDashes > 0) ? ::std::max< sal_Int32 >( nDashLen / nDashes, 1 ) : 0; 137 orLineDash.Dashes = nDashes; 138 orLineDash.Distance = ::std::max< sal_Int32 >( nDistance / rCustomDash.size(), 1 ); 139 } 140 141 DashStyle lclGetDashStyle( sal_Int32 nToken ) 142 { 143 switch( nToken ) 144 { 145 case XML_rnd: return DashStyle_ROUNDRELATIVE; 146 case XML_sq: return DashStyle_RECTRELATIVE; 147 case XML_flat: return DashStyle_RECT; 148 } 149 return DashStyle_ROUNDRELATIVE; 150 } 151 152 LineJoint lclGetLineJoint( sal_Int32 nToken ) 153 { 154 switch( nToken ) 155 { 156 case XML_round: return LineJoint_ROUND; 157 case XML_bevel: return LineJoint_BEVEL; 158 case XML_miter: return LineJoint_MITER; 159 } 160 return LineJoint_ROUND; 161 } 162 163 const sal_Int32 OOX_ARROWSIZE_SMALL = 0; 164 const sal_Int32 OOX_ARROWSIZE_MEDIUM = 1; 165 const sal_Int32 OOX_ARROWSIZE_LARGE = 2; 166 167 sal_Int32 lclGetArrowSize( sal_Int32 nToken ) 168 { 169 switch( nToken ) 170 { 171 case XML_sm: return OOX_ARROWSIZE_SMALL; 172 case XML_med: return OOX_ARROWSIZE_MEDIUM; 173 case XML_lg: return OOX_ARROWSIZE_LARGE; 174 } 175 return OOX_ARROWSIZE_MEDIUM; 176 } 177 178 // ---------------------------------------------------------------------------- 179 180 void lclPushMarkerProperties( ShapePropertyMap& rPropMap, 181 const LineArrowProperties& rArrowProps, sal_Int32 nLineWidth, bool bLineEnd ) 182 { 183 /* Store the marker polygon and the marker name in a single value, to be 184 able to pass both to the ShapePropertyMap::setProperty() function. */ 185 NamedValue aNamedMarker; 186 187 OUStringBuffer aBuffer; 188 sal_Int32 nMarkerWidth = 0; 189 bool bMarkerCenter = false; 190 sal_Int32 nArrowType = rArrowProps.moArrowType.get( XML_none ); 191 switch( nArrowType ) 192 { 193 case XML_triangle: 194 aBuffer.append( CREATE_OUSTRING( "msArrowEnd" ) ); 195 break; 196 case XML_arrow: 197 aBuffer.append( CREATE_OUSTRING( "msArrowOpenEnd" ) ); 198 break; 199 case XML_stealth: 200 aBuffer.append( CREATE_OUSTRING( "msArrowStealthEnd" ) ); 201 break; 202 case XML_diamond: 203 aBuffer.append( CREATE_OUSTRING( "msArrowDiamondEnd" ) ); 204 bMarkerCenter = true; 205 break; 206 case XML_oval: 207 aBuffer.append( CREATE_OUSTRING( "msArrowOvalEnd" ) ); 208 bMarkerCenter = true; 209 break; 210 } 211 212 if( aBuffer.getLength() > 0 ) 213 { 214 sal_Int32 nLength = lclGetArrowSize( rArrowProps.moArrowLength.get( XML_med ) ); 215 sal_Int32 nWidth = lclGetArrowSize( rArrowProps.moArrowWidth.get( XML_med ) ); 216 217 sal_Int32 nNameIndex = nWidth * 3 + nLength + 1; 218 aBuffer.append( sal_Unicode( ' ' ) ).append( nNameIndex ); 219 OUString aMarkerName = aBuffer.makeStringAndClear(); 220 221 bool bIsArrow = nArrowType == XML_arrow; 222 double fArrowLength = 1.0; 223 switch( nLength ) 224 { 225 case OOX_ARROWSIZE_SMALL: fArrowLength = (bIsArrow ? 3.5 : 2.0); break; 226 case OOX_ARROWSIZE_MEDIUM: fArrowLength = (bIsArrow ? 4.5 : 3.0); break; 227 case OOX_ARROWSIZE_LARGE: fArrowLength = (bIsArrow ? 6.0 : 5.0); break; 228 } 229 double fArrowWidth = 1.0; 230 switch( nWidth ) 231 { 232 case OOX_ARROWSIZE_SMALL: fArrowWidth = (bIsArrow ? 3.5 : 2.0); break; 233 case OOX_ARROWSIZE_MEDIUM: fArrowWidth = (bIsArrow ? 4.5 : 3.0); break; 234 case OOX_ARROWSIZE_LARGE: fArrowWidth = (bIsArrow ? 6.0 : 5.0); break; 235 } 236 // set arrow width relative to line width 237 sal_Int32 nBaseLineWidth = ::std::max< sal_Int32 >( nLineWidth, 70 ); 238 nMarkerWidth = static_cast< sal_Int32 >( fArrowWidth * nBaseLineWidth ); 239 240 /* Test if the marker already exists in the marker table, do not 241 create it again in this case. If markers are inserted explicitly 242 instead by their name, the polygon will be created always. 243 TODO: this can be optimized by using a map. */ 244 if( !rPropMap.hasNamedLineMarkerInTable( aMarkerName ) ) 245 { 246 // pass X and Y as percentage to OOX_ARROW_POINT 247 #define OOX_ARROW_POINT( x, y ) Point( static_cast< sal_Int32 >( fArrowWidth * x ), static_cast< sal_Int32 >( fArrowLength * y ) ) 248 249 ::std::vector< Point > aPoints; 250 switch( rArrowProps.moArrowType.get() ) 251 { 252 case XML_triangle: 253 aPoints.push_back( OOX_ARROW_POINT( 50, 0 ) ); 254 aPoints.push_back( OOX_ARROW_POINT( 100, 100 ) ); 255 aPoints.push_back( OOX_ARROW_POINT( 0, 100 ) ); 256 aPoints.push_back( OOX_ARROW_POINT( 50, 0 ) ); 257 break; 258 case XML_arrow: 259 aPoints.push_back( OOX_ARROW_POINT( 50, 0 ) ); 260 aPoints.push_back( OOX_ARROW_POINT( 100, 91 ) ); 261 aPoints.push_back( OOX_ARROW_POINT( 85, 100 ) ); 262 aPoints.push_back( OOX_ARROW_POINT( 50, 36 ) ); 263 aPoints.push_back( OOX_ARROW_POINT( 15, 100 ) ); 264 aPoints.push_back( OOX_ARROW_POINT( 0, 91 ) ); 265 aPoints.push_back( OOX_ARROW_POINT( 50, 0 ) ); 266 break; 267 case XML_stealth: 268 aPoints.push_back( OOX_ARROW_POINT( 50, 0 ) ); 269 aPoints.push_back( OOX_ARROW_POINT( 100, 100 ) ); 270 aPoints.push_back( OOX_ARROW_POINT( 50, 60 ) ); 271 aPoints.push_back( OOX_ARROW_POINT( 0, 100 ) ); 272 aPoints.push_back( OOX_ARROW_POINT( 50, 0 ) ); 273 break; 274 case XML_diamond: 275 aPoints.push_back( OOX_ARROW_POINT( 50, 0 ) ); 276 aPoints.push_back( OOX_ARROW_POINT( 100, 50 ) ); 277 aPoints.push_back( OOX_ARROW_POINT( 50, 100 ) ); 278 aPoints.push_back( OOX_ARROW_POINT( 0, 50 ) ); 279 aPoints.push_back( OOX_ARROW_POINT( 50, 0 ) ); 280 break; 281 case XML_oval: 282 aPoints.push_back( OOX_ARROW_POINT( 50, 0 ) ); 283 aPoints.push_back( OOX_ARROW_POINT( 75, 7 ) ); 284 aPoints.push_back( OOX_ARROW_POINT( 93, 25 ) ); 285 aPoints.push_back( OOX_ARROW_POINT( 100, 50 ) ); 286 aPoints.push_back( OOX_ARROW_POINT( 93, 75 ) ); 287 aPoints.push_back( OOX_ARROW_POINT( 75, 93 ) ); 288 aPoints.push_back( OOX_ARROW_POINT( 50, 100 ) ); 289 aPoints.push_back( OOX_ARROW_POINT( 25, 93 ) ); 290 aPoints.push_back( OOX_ARROW_POINT( 7, 75 ) ); 291 aPoints.push_back( OOX_ARROW_POINT( 0, 50 ) ); 292 aPoints.push_back( OOX_ARROW_POINT( 7, 25 ) ); 293 aPoints.push_back( OOX_ARROW_POINT( 25, 7 ) ); 294 aPoints.push_back( OOX_ARROW_POINT( 50, 0 ) ); 295 break; 296 } 297 #undef OOX_ARROW_POINT 298 299 OSL_ENSURE( !aPoints.empty(), "lclPushMarkerProperties - missing arrow coordinates" ); 300 if( !aPoints.empty() ) 301 { 302 PolyPolygonBezierCoords aMarkerCoords; 303 aMarkerCoords.Coordinates.realloc( 1 ); 304 aMarkerCoords.Coordinates[ 0 ] = ContainerHelper::vectorToSequence( aPoints ); 305 306 ::std::vector< PolygonFlags > aFlags( aPoints.size(), PolygonFlags_NORMAL ); 307 aMarkerCoords.Flags.realloc( 1 ); 308 aMarkerCoords.Flags[ 0 ] = ContainerHelper::vectorToSequence( aFlags ); 309 310 aNamedMarker.Name = aMarkerName; 311 aNamedMarker.Value <<= aMarkerCoords; 312 } 313 } 314 else 315 { 316 /* Named marker object exists already in the marker table, pass 317 its name only. This will set the name as property value, but 318 does not create a new object in the marker table. */ 319 aNamedMarker.Name = aMarkerName; 320 } 321 } 322 323 // push the properties (filled aNamedMarker.Name indicates valid marker) 324 if( aNamedMarker.Name.getLength() > 0 ) 325 { 326 if( bLineEnd ) 327 { 328 rPropMap.setProperty( SHAPEPROP_LineEnd, aNamedMarker ); 329 rPropMap.setProperty( SHAPEPROP_LineEndWidth, nMarkerWidth ); 330 rPropMap.setProperty( SHAPEPROP_LineEndCenter, bMarkerCenter ); 331 } 332 else 333 { 334 rPropMap.setProperty( SHAPEPROP_LineStart, aNamedMarker ); 335 rPropMap.setProperty( SHAPEPROP_LineStartWidth, nMarkerWidth ); 336 rPropMap.setProperty( SHAPEPROP_LineStartCenter, bMarkerCenter ); 337 } 338 } 339 } 340 341 } // namespace 342 343 // ============================================================================ 344 345 void LineArrowProperties::assignUsed( const LineArrowProperties& rSourceProps ) 346 { 347 moArrowType.assignIfUsed( rSourceProps.moArrowType ); 348 moArrowWidth.assignIfUsed( rSourceProps.moArrowWidth ); 349 moArrowLength.assignIfUsed( rSourceProps.moArrowLength ); 350 } 351 352 // ============================================================================ 353 354 void LineProperties::assignUsed( const LineProperties& rSourceProps ) 355 { 356 maStartArrow.assignUsed( rSourceProps.maStartArrow ); 357 maEndArrow.assignUsed( rSourceProps.maEndArrow ); 358 maLineFill.assignUsed( rSourceProps.maLineFill ); 359 if( !rSourceProps.maCustomDash.empty() ) 360 maCustomDash = rSourceProps.maCustomDash; 361 moLineWidth.assignIfUsed( rSourceProps.moLineWidth ); 362 moPresetDash.assignIfUsed( rSourceProps.moPresetDash ); 363 moLineCompound.assignIfUsed( rSourceProps.moLineCompound ); 364 moLineCap.assignIfUsed( rSourceProps.moLineCap ); 365 moLineJoint.assignIfUsed( rSourceProps.moLineJoint ); 366 } 367 368 void LineProperties::pushToPropMap( ShapePropertyMap& rPropMap, 369 const GraphicHelper& rGraphicHelper, sal_Int32 nPhClr ) const 370 { 371 // line fill type must exist, otherwise ignore other properties 372 if( maLineFill.moFillType.has() ) 373 { 374 // line style (our core only supports none and solid) 375 LineStyle eLineStyle = (maLineFill.moFillType.get() == XML_noFill) ? LineStyle_NONE : LineStyle_SOLID; 376 377 // convert line width from EMUs to 1/100mm 378 sal_Int32 nLineWidth = convertEmuToHmm( moLineWidth.get( 0 ) ); 379 380 // create line dash from preset dash token (not for invisible line) 381 if( (eLineStyle != LineStyle_NONE) && (moPresetDash.differsFrom( XML_solid ) || (!moPresetDash && !maCustomDash.empty())) ) 382 { 383 LineDash aLineDash; 384 aLineDash.Style = lclGetDashStyle( moLineCap.get( XML_rnd ) ); 385 386 // convert preset dash or custom dash 387 if( moPresetDash.has() ) 388 lclConvertPresetDash( aLineDash, moPresetDash.get() ); 389 else 390 lclConvertCustomDash( aLineDash, maCustomDash ); 391 392 // convert relative dash/dot length to absolute length 393 sal_Int32 nBaseLineWidth = ::std::max< sal_Int32 >( nLineWidth, 35 ); 394 aLineDash.DotLen *= nBaseLineWidth; 395 aLineDash.DashLen *= nBaseLineWidth; 396 aLineDash.Distance *= nBaseLineWidth; 397 398 if( rPropMap.setProperty( SHAPEPROP_LineDash, aLineDash ) ) 399 eLineStyle = LineStyle_DASH; 400 } 401 402 // set final line style property 403 rPropMap.setProperty( SHAPEPROP_LineStyle, eLineStyle ); 404 405 // line joint type 406 if( moLineJoint.has() ) 407 rPropMap.setProperty( SHAPEPROP_LineJoint, lclGetLineJoint( moLineJoint.get() ) ); 408 409 // line width in 1/100mm 410 rPropMap.setProperty( SHAPEPROP_LineWidth, nLineWidth ); 411 412 // line color and transparence 413 Color aLineColor = maLineFill.getBestSolidColor(); 414 if( aLineColor.isUsed() ) 415 { 416 rPropMap.setProperty( SHAPEPROP_LineColor, aLineColor.getColor( rGraphicHelper, nPhClr ) ); 417 if( aLineColor.hasTransparency() ) 418 rPropMap.setProperty( SHAPEPROP_LineTransparency, aLineColor.getTransparency() ); 419 } 420 421 // line markers 422 lclPushMarkerProperties( rPropMap, maStartArrow, nLineWidth, false ); 423 lclPushMarkerProperties( rPropMap, maEndArrow, nLineWidth, true ); 424 } 425 } 426 427 // ============================================================================ 428 429 } // namespace drawingml 430 } // namespace oox 431 432