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