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