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