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