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