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/lineproperties.hxx>
21 #include <rtl/ustrbuf.hxx>
22 #include <osl/diagnose.h>
23 #include <com/sun/star/beans/NamedValue.hpp>
24 #include <com/sun/star/drawing/LineCap.hpp>
25 #include <com/sun/star/drawing/LineDash.hpp>
26 #include <com/sun/star/drawing/LineJoint.hpp>
27 #include <com/sun/star/drawing/LineStyle.hpp>
28 #include <com/sun/star/drawing/PolyPolygonBezierCoords.hpp>
29 #include <oox/drawingml/drawingmltypes.hxx>
30 #include <oox/drawingml/shapepropertymap.hxx>
31 #include <oox/helper/containerhelper.hxx>
32 #include <oox/helper/graphichelper.hxx>
33 #include <oox/token/tokens.hxx>
34 
35 using namespace ::com::sun::star;
36 using namespace ::com::sun::star::beans;
37 using namespace ::com::sun::star::drawing;
38 
39 
40 namespace oox::drawingml {
41 
42 namespace {
43 
lclSetDashData(LineDash & orLineDash,sal_Int16 nDots,sal_Int32 nDotLen,sal_Int16 nDashes,sal_Int32 nDashLen,sal_Int32 nDistance)44 void lclSetDashData( LineDash& orLineDash, sal_Int16 nDots, sal_Int32 nDotLen,
45         sal_Int16 nDashes, sal_Int32 nDashLen, sal_Int32 nDistance )
46 {
47     orLineDash.Dots = nDots;
48     orLineDash.DotLen = nDotLen;
49     orLineDash.Dashes = nDashes;
50     orLineDash.DashLen = nDashLen;
51     orLineDash.Distance = nDistance;
52 }
53 
54 /** Converts the specified preset dash to API dash.
55  */
lclConvertPresetDash(LineDash & orLineDash,sal_Int32 nPresetDash)56 void lclConvertPresetDash(LineDash& orLineDash, sal_Int32 nPresetDash)
57 {
58     switch( nPresetDash )
59     {
60         case XML_dot:           lclSetDashData( orLineDash, 1, 1, 0, 0, 3 );    break;
61         case XML_dash:          lclSetDashData( orLineDash, 1, 4, 0, 0, 3 );    break;
62         case XML_dashDot:       lclSetDashData( orLineDash, 1, 4, 1, 1, 3 );    break;
63 
64         case XML_lgDash:        lclSetDashData( orLineDash, 1, 8, 0, 0, 3 );    break;
65         case XML_lgDashDot:     lclSetDashData( orLineDash, 1, 8, 1, 1, 3 );    break;
66         case XML_lgDashDotDot:  lclSetDashData( orLineDash, 1, 8, 2, 1, 3 );    break;
67 
68         case XML_sysDot:        lclSetDashData( orLineDash, 1, 1, 0, 0, 1 );    break;
69         case XML_sysDash:       lclSetDashData( orLineDash, 1, 3, 0, 0, 1 );    break;
70         case XML_sysDashDot:    lclSetDashData( orLineDash, 1, 3, 1, 1, 1 );    break;
71         case XML_sysDashDotDot: lclSetDashData( orLineDash, 1, 3, 2, 1, 1 );    break;
72 
73         default:
74             OSL_FAIL( "lclConvertPresetDash - unsupported preset dash" );
75             lclSetDashData( orLineDash, 1, 4, 0, 0, 3 );
76     }
77     orLineDash.DotLen *= 100;
78     orLineDash.DashLen *= 100;
79     orLineDash.Distance *= 100;
80 }
81 
82 /** Converts the passed custom dash to API dash. rCustomDash should not be empty.
83  * We assume, that there exist only two length values and the distance is the same
84  * for all dashes. Other kind of dash stop sequences cannot be represented, neither
85  * in model nor in ODF.
86  */
lclConvertCustomDash(LineDash & orLineDash,const LineProperties::DashStopVector & rCustomDash)87 void lclConvertCustomDash(LineDash& orLineDash, const LineProperties::DashStopVector& rCustomDash)
88 {
89     OSL_ASSERT(!rCustomDash.empty());
90     // Assume all dash stops have the same sp values.
91     orLineDash.Distance = rCustomDash[0].second;
92     // First kind of dashes go to "Dots"
93     orLineDash.DotLen = rCustomDash[0].first;
94     orLineDash.Dots = 0;
95     for(const auto& rIt : rCustomDash)
96     {
97         if (rIt.first != orLineDash.DotLen)
98             break;
99         ++orLineDash.Dots;
100     }
101     // All others go to "Dashes", we cannot handle more than two kinds.
102     orLineDash.Dashes = rCustomDash.size() - orLineDash.Dots;
103     if (orLineDash.Dashes > 0)
104         orLineDash.DashLen = rCustomDash[orLineDash.Dots].first;
105     else
106         orLineDash.DashLen = 0;
107 
108     // convert to API, e.g. 123% is 123000 in MS Office and 123 in our API
109     orLineDash.DotLen = orLineDash.DotLen / 1000;
110     orLineDash.DashLen = orLineDash.DashLen / 1000;
111     orLineDash.Distance = orLineDash.Distance / 1000;
112 }
113 
114 /** LibreOffice uses value 0, if a length attribute is missing in the
115  * style definition, but treats it as 100%.
116  * LibreOffice uses absolute values in some style definitions. Try to
117  * reconstruct them from the imported relative values.
118  */
lclRecoverStandardDashStyles(LineDash & orLineDash,sal_Int32 nLineWidth)119 void lclRecoverStandardDashStyles(LineDash& orLineDash, sal_Int32 nLineWidth)
120 {
121     sal_uInt16 nDots = orLineDash.Dots;
122     sal_uInt16 nDashes = orLineDash.Dashes;
123     sal_uInt32 nDotLen = orLineDash.DotLen;
124     sal_uInt32 nDashLen = orLineDash.DashLen;
125     sal_uInt32 nDistance = orLineDash.Distance;
126     // Use same ersatz for hairline as in export.
127     double fWidthHelp = nLineWidth == 0 ? 26.95/100.0 : nLineWidth / 100.0;
128     // start with (var) cases, because they have no rounding problems
129     // "Fine Dashed", "Line Style 9" and "Dashed (var)" need no recover
130     if (nDots == 3 && nDotLen == 197 &&nDashes == 3 && nDashLen == 100 && nDistance == 100)
131     {   // "3 Dashes 3 Dots (var)"
132         orLineDash.DashLen = 0;
133     }
134     else if (nDots == 1 && nDotLen == 100 && nDashes == 0 && nDistance == 50)
135     {   // "Ultrafine Dotted (var)"
136         orLineDash.DotLen = 0;
137     }
138     else if (nDots == 2 && nDashes == 0 && nDotLen == nDistance
139         && std::abs(nDistance * fWidthHelp - 51.0) < fWidthHelp)
140     {   // "Ultrafine Dashed"
141         orLineDash.Dots = 1;
142         orLineDash.DotLen = 51;
143         orLineDash.Dashes = 1;
144         orLineDash.DashLen = 51;
145         orLineDash.Distance = 51;
146         orLineDash.Style = orLineDash.Style == DashStyle_ROUNDRELATIVE ? DashStyle_ROUND : DashStyle_RECT;
147     }
148     else if (nDots == 2 && nDashes == 3 && std::abs(nDotLen * fWidthHelp - 51.0) < fWidthHelp
149         && std::abs(nDashLen * fWidthHelp - 254.0) < fWidthHelp
150         && std::abs(nDistance * fWidthHelp - 127.0) < fWidthHelp)
151     {   // "Ultrafine 2 Dots 3 Dashes"
152         orLineDash.DotLen = 51;
153         orLineDash.DashLen = 254;
154         orLineDash.Distance = 127;
155         orLineDash.Style = orLineDash.Style == DashStyle_ROUNDRELATIVE ? DashStyle_ROUND : DashStyle_RECT;
156     }
157     else if (nDots == 1 && nDotLen == 100 && nDashes == 0
158         && std::abs(nDistance * fWidthHelp - 457.0) < fWidthHelp)
159     {    // "Fine Dotted"
160         orLineDash.DotLen = 0;
161         orLineDash.Distance = 457;
162         orLineDash.Style = orLineDash.Style == DashStyle_ROUNDRELATIVE ? DashStyle_ROUND : DashStyle_RECT;
163     }
164     else if (nDots == 1 && nDashes == 10 && nDashLen == 100
165         && std::abs(nDistance * fWidthHelp - 152.0) < fWidthHelp)
166     {   // "Line with Fine Dots"
167         orLineDash.DotLen = 2007;
168         orLineDash.DashLen = 0;
169         orLineDash.Distance = 152;
170         orLineDash.Style = orLineDash.Style == DashStyle_ROUNDRELATIVE ? DashStyle_ROUND : DashStyle_RECT;
171     }
172     else if (nDots == 2 && nDotLen == 100 && nDashes == 1 && nDashLen == nDistance
173         && std::abs(nDistance * fWidthHelp - 203.0) < fWidthHelp)
174     {   // "2 Dots 1 Dash"
175         orLineDash.DotLen = 0;
176         orLineDash.DashLen = 203;
177         orLineDash.Distance = 203;
178         orLineDash.Style = orLineDash.Style == DashStyle_ROUNDRELATIVE ? DashStyle_ROUND : DashStyle_RECT;
179     }
180 }
181 
lclGetDashStyle(sal_Int32 nToken)182 DashStyle lclGetDashStyle( sal_Int32 nToken )
183 {
184     OSL_ASSERT((nToken & sal_Int32(0xFFFF0000))==0);
185     // MS Office dashing is always relative to line width
186     switch( nToken )
187     {
188         case XML_rnd:   return DashStyle_ROUNDRELATIVE;
189         case XML_sq:    return DashStyle_RECTRELATIVE; // default in OOXML
190         case XML_flat:  return DashStyle_RECTRELATIVE; // default in MS Office
191     }
192     return DashStyle_RECTRELATIVE;
193 }
194 
lclGetLineCap(sal_Int32 nToken)195 LineCap lclGetLineCap( sal_Int32 nToken )
196 {
197     OSL_ASSERT((nToken & sal_Int32(0xFFFF0000))==0);
198     switch( nToken )
199     {
200         case XML_rnd:   return LineCap_ROUND;
201         case XML_sq:    return LineCap_SQUARE; // default in OOXML
202         case XML_flat:  return LineCap_BUTT; // default in MS Office
203     }
204     return LineCap_BUTT;
205 }
206 
lclGetLineJoint(sal_Int32 nToken)207 LineJoint lclGetLineJoint( sal_Int32 nToken )
208 {
209     OSL_ASSERT((nToken & sal_Int32(0xFFFF0000))==0);
210     switch( nToken )
211     {
212         case XML_round: return LineJoint_ROUND;
213         case XML_bevel: return LineJoint_BEVEL;
214         case XML_miter: return LineJoint_MITER;
215     }
216     return LineJoint_ROUND;
217 }
218 
219 const sal_Int32 OOX_ARROWSIZE_SMALL     = 0;
220 const sal_Int32 OOX_ARROWSIZE_MEDIUM    = 1;
221 const sal_Int32 OOX_ARROWSIZE_LARGE     = 2;
222 
lclGetArrowSize(sal_Int32 nToken)223 sal_Int32 lclGetArrowSize( sal_Int32 nToken )
224 {
225     OSL_ASSERT((nToken & sal_Int32(0xFFFF0000))==0);
226     switch( nToken )
227     {
228         case XML_sm:    return OOX_ARROWSIZE_SMALL;
229         case XML_med:   return OOX_ARROWSIZE_MEDIUM;
230         case XML_lg:    return OOX_ARROWSIZE_LARGE;
231     }
232     return OOX_ARROWSIZE_MEDIUM;
233 }
234 
lclPushMarkerProperties(ShapePropertyMap & rPropMap,const LineArrowProperties & rArrowProps,sal_Int32 nLineWidth,bool bLineEnd)235 void lclPushMarkerProperties( ShapePropertyMap& rPropMap,
236         const LineArrowProperties& rArrowProps, sal_Int32 nLineWidth, bool bLineEnd )
237 {
238     /*  Store the marker polygon and the marker name in a single value, to be
239         able to pass both to the ShapePropertyMap::setProperty() function. */
240     NamedValue aNamedMarker;
241 
242     OUStringBuffer aBuffer;
243     sal_Int32 nMarkerWidth = 0;
244     bool bMarkerCenter = false;
245     sal_Int32 nArrowType = rArrowProps.moArrowType.get( XML_none );
246     OSL_ASSERT((nArrowType & sal_Int32(0xFFFF0000))==0);
247     switch( nArrowType )
248     {
249         case XML_triangle:
250             aBuffer.append( "msArrowEnd" );
251         break;
252         case XML_arrow:
253             aBuffer.append( "msArrowOpenEnd" );
254         break;
255         case XML_stealth:
256             aBuffer.append( "msArrowStealthEnd" );
257         break;
258         case XML_diamond:
259             aBuffer.append( "msArrowDiamondEnd" );
260             bMarkerCenter = true;
261         break;
262         case XML_oval:
263             aBuffer.append( "msArrowOvalEnd" );
264             bMarkerCenter = true;
265         break;
266     }
267 
268     if( !aBuffer.isEmpty() )
269     {
270         bool bIsArrow = nArrowType == XML_arrow;
271         sal_Int32 nLength = lclGetArrowSize( rArrowProps.moArrowLength.get( XML_med ) );
272         sal_Int32 nWidth  = lclGetArrowSize( rArrowProps.moArrowWidth.get( XML_med ) );
273 
274         sal_Int32 nNameIndex = nWidth * 3 + nLength + 1;
275         aBuffer.append( ' ' ).append( nNameIndex );
276         if (bIsArrow)
277         {
278             // Arrow marker form depends also on line width
279             aBuffer.append(' ').append(nLineWidth);
280         }
281         OUString aMarkerName = aBuffer.makeStringAndClear();
282 
283         double fArrowLength = 1.0;
284         switch( nLength )
285         {
286             case OOX_ARROWSIZE_SMALL:   fArrowLength = (bIsArrow ? 2.5 : 2.0); break;
287             case OOX_ARROWSIZE_MEDIUM:  fArrowLength = (bIsArrow ? 3.5 : 3.0); break;
288             case OOX_ARROWSIZE_LARGE:   fArrowLength = (bIsArrow ? 5.5 : 5.0); break;
289         }
290         double fArrowWidth = 1.0;
291         switch( nWidth )
292         {
293             case OOX_ARROWSIZE_SMALL:   fArrowWidth = (bIsArrow ? 2.5 : 2.0);  break;
294             case OOX_ARROWSIZE_MEDIUM:  fArrowWidth = (bIsArrow ? 3.5 : 3.0);  break;
295             case OOX_ARROWSIZE_LARGE:   fArrowWidth = (bIsArrow ? 5.5 : 5.0);  break;
296         }
297         // set arrow width relative to line width
298         sal_Int32 nBaseLineWidth = ::std::max< sal_Int32 >( nLineWidth, 70 );
299         nMarkerWidth = static_cast<sal_Int32>( fArrowWidth * nBaseLineWidth );
300 
301         /*  Test if the marker already exists in the marker table, do not
302             create it again in this case. If markers are inserted explicitly
303             instead by their name, the polygon will be created always.
304             TODO: this can be optimized by using a map. */
305         if( !rPropMap.hasNamedLineMarkerInTable( aMarkerName ) )
306         {
307             // pass X and Y as percentage to OOX_ARROW_POINT
308             auto OOX_ARROW_POINT = [fArrowLength, fArrowWidth]( double x, double y ) { return awt::Point( static_cast< sal_Int32 >( fArrowWidth * x ), static_cast< sal_Int32 >( fArrowLength * y ) ); };
309             // tdf#100491 Arrow line marker, unlike other markers, depends on line width.
310             // So calculate width of half line (more convenient during drawing) taking into account
311             // further conversions/scaling done in OOX_ARROW_POINT and scaling to nMarkerWidth.
312             const double fArrowLineHalfWidth = ::std::max< double >( 100.0 * 0.5 * nLineWidth / nMarkerWidth, 1 );
313 
314             ::std::vector< awt::Point > aPoints;
315             OSL_ASSERT((rArrowProps.moArrowType.get() & sal_Int32(0xFFFF0000))==0);
316             switch( rArrowProps.moArrowType.get() )
317             {
318                 case XML_triangle:
319                     aPoints.push_back( OOX_ARROW_POINT(  50,   0 ) );
320                     aPoints.push_back( OOX_ARROW_POINT( 100, 100 ) );
321                     aPoints.push_back( OOX_ARROW_POINT(   0, 100 ) );
322                     aPoints.push_back( OOX_ARROW_POINT(  50,   0 ) );
323                 break;
324                 case XML_arrow:
325                     aPoints.push_back( OOX_ARROW_POINT( 50, 0 ) );
326                     aPoints.push_back( OOX_ARROW_POINT( 100, 100 - fArrowLineHalfWidth * 1.5) );
327                     aPoints.push_back( OOX_ARROW_POINT( 100 - fArrowLineHalfWidth * 1.5, 100 ) );
328                     aPoints.push_back( OOX_ARROW_POINT( 50.0 + fArrowLineHalfWidth, 5.5 * fArrowLineHalfWidth) );
329                     aPoints.push_back( OOX_ARROW_POINT( 50.0 + fArrowLineHalfWidth, 100 ) );
330                     aPoints.push_back( OOX_ARROW_POINT( 50.0 - fArrowLineHalfWidth, 100 ) );
331                     aPoints.push_back( OOX_ARROW_POINT( 50.0 - fArrowLineHalfWidth, 5.5 * fArrowLineHalfWidth) );
332                     aPoints.push_back( OOX_ARROW_POINT( fArrowLineHalfWidth * 1.5, 100 ) );
333                     aPoints.push_back( OOX_ARROW_POINT( 0, 100 - fArrowLineHalfWidth * 1.5) );
334                     aPoints.push_back( OOX_ARROW_POINT( 50, 0 ) );
335                 break;
336                 case XML_stealth:
337                     aPoints.push_back( OOX_ARROW_POINT(  50,   0 ) );
338                     aPoints.push_back( OOX_ARROW_POINT( 100, 100 ) );
339                     aPoints.push_back( OOX_ARROW_POINT(  50,  60 ) );
340                     aPoints.push_back( OOX_ARROW_POINT(   0, 100 ) );
341                     aPoints.push_back( OOX_ARROW_POINT(  50,   0 ) );
342                 break;
343                 case XML_diamond:
344                     aPoints.push_back( OOX_ARROW_POINT(  50,   0 ) );
345                     aPoints.push_back( OOX_ARROW_POINT( 100,  50 ) );
346                     aPoints.push_back( OOX_ARROW_POINT(  50, 100 ) );
347                     aPoints.push_back( OOX_ARROW_POINT(   0,  50 ) );
348                     aPoints.push_back( OOX_ARROW_POINT(  50,   0 ) );
349                 break;
350                 case XML_oval:
351                     aPoints.push_back( OOX_ARROW_POINT(  50,   0 ) );
352                     aPoints.push_back( OOX_ARROW_POINT(  75,   7 ) );
353                     aPoints.push_back( OOX_ARROW_POINT(  93,  25 ) );
354                     aPoints.push_back( OOX_ARROW_POINT( 100,  50 ) );
355                     aPoints.push_back( OOX_ARROW_POINT(  93,  75 ) );
356                     aPoints.push_back( OOX_ARROW_POINT(  75,  93 ) );
357                     aPoints.push_back( OOX_ARROW_POINT(  50, 100 ) );
358                     aPoints.push_back( OOX_ARROW_POINT(  25,  93 ) );
359                     aPoints.push_back( OOX_ARROW_POINT(   7,  75 ) );
360                     aPoints.push_back( OOX_ARROW_POINT(   0,  50 ) );
361                     aPoints.push_back( OOX_ARROW_POINT(   7,  25 ) );
362                     aPoints.push_back( OOX_ARROW_POINT(  25,   7 ) );
363                     aPoints.push_back( OOX_ARROW_POINT(  50,   0 ) );
364                 break;
365             }
366 
367             OSL_ENSURE( !aPoints.empty(), "lclPushMarkerProperties - missing arrow coordinates" );
368             if( !aPoints.empty() )
369             {
370                 PolyPolygonBezierCoords aMarkerCoords;
371                 aMarkerCoords.Coordinates.realloc( 1 );
372                 aMarkerCoords.Coordinates[ 0 ] = ContainerHelper::vectorToSequence( aPoints );
373 
374                 ::std::vector< PolygonFlags > aFlags( aPoints.size(), PolygonFlags_NORMAL );
375                 aMarkerCoords.Flags.realloc( 1 );
376                 aMarkerCoords.Flags[ 0 ] = ContainerHelper::vectorToSequence( aFlags );
377 
378                 aNamedMarker.Name = aMarkerName;
379                 aNamedMarker.Value <<= aMarkerCoords;
380             }
381         }
382         else
383         {
384             /*  Named marker object exists already in the marker table, pass
385                 its name only. This will set the name as property value, but
386                 does not create a new object in the marker table. */
387             aNamedMarker.Name = aMarkerName;
388         }
389     }
390 
391     // push the properties (filled aNamedMarker.Name indicates valid marker)
392     if( aNamedMarker.Name.isEmpty() )
393         return;
394 
395     if( bLineEnd )
396     {
397         rPropMap.setProperty( ShapeProperty::LineEnd, aNamedMarker );
398         rPropMap.setProperty( ShapeProperty::LineEndWidth, nMarkerWidth );
399         rPropMap.setProperty( ShapeProperty::LineEndCenter, bMarkerCenter );
400     }
401     else
402     {
403         rPropMap.setProperty( ShapeProperty::LineStart, aNamedMarker );
404         rPropMap.setProperty( ShapeProperty::LineStartWidth, nMarkerWidth );
405         rPropMap.setProperty( ShapeProperty::LineStartCenter, bMarkerCenter );
406     }
407 }
408 
409 } // namespace
410 
assignUsed(const LineArrowProperties & rSourceProps)411 void LineArrowProperties::assignUsed( const LineArrowProperties& rSourceProps )
412 {
413     moArrowType.assignIfUsed( rSourceProps.moArrowType );
414     moArrowWidth.assignIfUsed( rSourceProps.moArrowWidth );
415     moArrowLength.assignIfUsed( rSourceProps.moArrowLength );
416 }
417 
assignUsed(const LineProperties & rSourceProps)418 void LineProperties::assignUsed( const LineProperties& rSourceProps )
419 {
420     maStartArrow.assignUsed( rSourceProps.maStartArrow );
421     maEndArrow.assignUsed( rSourceProps.maEndArrow );
422     maLineFill.assignUsed( rSourceProps.maLineFill );
423     if( !rSourceProps.maCustomDash.empty() )
424         maCustomDash = rSourceProps.maCustomDash;
425     moLineWidth.assignIfUsed( rSourceProps.moLineWidth );
426     moPresetDash.assignIfUsed( rSourceProps.moPresetDash );
427     moLineCompound.assignIfUsed( rSourceProps.moLineCompound );
428     moLineCap.assignIfUsed( rSourceProps.moLineCap );
429     moLineJoint.assignIfUsed( rSourceProps.moLineJoint );
430 }
431 
pushToPropMap(ShapePropertyMap & rPropMap,const GraphicHelper & rGraphicHelper,::Color nPhClr) const432 void LineProperties::pushToPropMap( ShapePropertyMap& rPropMap,
433         const GraphicHelper& rGraphicHelper, ::Color nPhClr ) const
434 {
435     // line fill type must exist, otherwise ignore other properties
436     if( !maLineFill.moFillType.has() )
437         return;
438 
439     // line style (our core only supports none and solid)
440     drawing::LineStyle eLineStyle = (maLineFill.moFillType.get() == XML_noFill) ? drawing::LineStyle_NONE : drawing::LineStyle_SOLID;
441 
442     // line width in 1/100mm
443     sal_Int32 nLineWidth = getLineWidth(); // includes conversion from EMUs to 1/100mm
444     rPropMap.setProperty( ShapeProperty::LineWidth, nLineWidth );
445 
446     // line cap type
447     LineCap eLineCap = moLineCap.has() ? lclGetLineCap( moLineCap.get() ) : LineCap_BUTT;
448     if( moLineCap.has() )
449         rPropMap.setProperty( ShapeProperty::LineCap, eLineCap );
450 
451     // create line dash from preset dash token or dash stop vector (not for invisible line)
452     if( (eLineStyle != drawing::LineStyle_NONE) && (moPresetDash.differsFrom( XML_solid ) || !maCustomDash.empty()) )
453     {
454         LineDash aLineDash;
455         aLineDash.Style = lclGetDashStyle( moLineCap.get( XML_flat ) );
456 
457         if(moPresetDash.differsFrom(XML_solid))
458             lclConvertPresetDash(aLineDash, moPresetDash.get(XML_dash));
459         else // !maCustomDash.empty()
460         {
461             lclConvertCustomDash(aLineDash, maCustomDash);
462             lclRecoverStandardDashStyles(aLineDash, nLineWidth);
463         }
464 
465         // In MS Office (2020) for preset dash style line caps round and square are included in dash length.
466         // For custom dash style round line cap is included, square line cap is added. In ODF line caps are
467         // always added to dash length. Tweak the length accordingly.
468         if (eLineCap == LineCap_ROUND || (eLineCap == LineCap_SQUARE && maCustomDash.empty()))
469         {
470             // Cannot use -100 because that results in 0 length in some cases and
471             // LibreOffice interprets 0 length as 100%.
472             if (aLineDash.DotLen >= 100 || aLineDash.DashLen >= 100)
473                 aLineDash.Distance += 99;
474             if (aLineDash.DotLen >= 100)
475                 aLineDash.DotLen -= 99;
476             if (aLineDash.DashLen >= 100)
477                 aLineDash.DashLen -= 99;
478         }
479 
480         if( rPropMap.setProperty( ShapeProperty::LineDash, aLineDash ) )
481             eLineStyle = drawing::LineStyle_DASH;
482     }
483 
484     // set final line style property
485     rPropMap.setProperty( ShapeProperty::LineStyle, eLineStyle );
486 
487     // line joint type
488     if( moLineJoint.has() )
489         rPropMap.setProperty( ShapeProperty::LineJoint, lclGetLineJoint( moLineJoint.get() ) );
490 
491     // line color and transparence
492     Color aLineColor = maLineFill.getBestSolidColor();
493     if( aLineColor.isUsed() )
494     {
495         rPropMap.setProperty( ShapeProperty::LineColor, aLineColor.getColor( rGraphicHelper, nPhClr ) );
496         if( aLineColor.hasTransparency() )
497             rPropMap.setProperty( ShapeProperty::LineTransparency, aLineColor.getTransparency() );
498     }
499 
500     // line markers
501     lclPushMarkerProperties( rPropMap, maStartArrow, nLineWidth, false );
502     lclPushMarkerProperties( rPropMap, maEndArrow,   nLineWidth, true );
503 }
504 
getLineStyle() const505 drawing::LineStyle LineProperties::getLineStyle() const
506 {
507     // rules to calculate the line style inferred from the code in LineProperties::pushToPropMap
508     return (maLineFill.moFillType.get() == XML_noFill) ?
509             drawing::LineStyle_NONE :
510             (moPresetDash.differsFrom( XML_solid ) || (!moPresetDash && !maCustomDash.empty())) ?
511                     drawing::LineStyle_DASH :
512                     drawing::LineStyle_SOLID;
513 }
514 
getLineCap() const515 drawing::LineCap LineProperties::getLineCap() const
516 {
517     if( moLineCap.has() )
518         return lclGetLineCap( moLineCap.get() );
519 
520     return drawing::LineCap_BUTT;
521 }
522 
getLineJoint() const523 drawing::LineJoint LineProperties::getLineJoint() const
524 {
525     if( moLineJoint.has() )
526         return lclGetLineJoint( moLineJoint.get() );
527 
528     return drawing::LineJoint_NONE;
529 }
530 
getLineWidth() const531 sal_Int32 LineProperties::getLineWidth() const
532 {
533     return convertEmuToHmm( moLineWidth.get( 0 ) );
534 }
535 
536 } // namespace oox
537 
538 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */
539