Coverage Report

Created: 2025-07-07 10:01

/src/libreoffice/oox/source/drawingml/lineproperties.cxx
Line
Count
Source (jump to first uncovered line)
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
48
void lclSetDashData( LineDash& orLineDash, sal_Int16 nDots, sal_Int32 nDotLen,
49
        sal_Int16 nDashes, sal_Int32 nDashLen, sal_Int32 nDistance )
50
124
{
51
124
    orLineDash.Dots = nDots;
52
124
    orLineDash.DotLen = nDotLen;
53
124
    orLineDash.Dashes = nDashes;
54
124
    orLineDash.DashLen = nDashLen;
55
124
    orLineDash.Distance = nDistance;
56
124
}
57
58
/** Converts the specified preset dash to API dash.
59
 */
60
void lclConvertPresetDash(LineDash& orLineDash, sal_Int32 nPresetDash)
61
124
{
62
124
    switch( nPresetDash )
63
124
    {
64
0
        case XML_dot:           lclSetDashData( orLineDash, 1, 1, 0, 0, 3 );    break;
65
19
        case XML_dash:          lclSetDashData( orLineDash, 1, 4, 0, 0, 3 );    break;
66
10
        case XML_dashDot:       lclSetDashData( orLineDash, 1, 4, 1, 1, 3 );    break;
67
68
32
        case XML_lgDash:        lclSetDashData( orLineDash, 1, 8, 0, 0, 3 );    break;
69
10
        case XML_lgDashDot:     lclSetDashData( orLineDash, 1, 8, 1, 1, 3 );    break;
70
10
        case XML_lgDashDotDot:  lclSetDashData( orLineDash, 1, 8, 2, 1, 3 );    break;
71
72
10
        case XML_sysDot:        lclSetDashData( orLineDash, 1, 1, 0, 0, 1 );    break;
73
33
        case XML_sysDash:       lclSetDashData( orLineDash, 1, 3, 0, 0, 1 );    break;
74
0
        case XML_sysDashDot:    lclSetDashData( orLineDash, 1, 3, 1, 1, 1 );    break;
75
0
        case XML_sysDashDotDot: lclSetDashData( orLineDash, 1, 3, 2, 1, 1 );    break;
76
77
0
        default:
78
0
            OSL_FAIL( "lclConvertPresetDash - unsupported preset dash" );
79
0
            lclSetDashData( orLineDash, 1, 4, 0, 0, 3 );
80
124
    }
81
124
    orLineDash.DotLen *= 100;
82
124
    orLineDash.DashLen *= 100;
83
124
    orLineDash.Distance *= 100;
84
124
}
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
 */
91
void lclConvertCustomDash(LineDash& orLineDash, const LineProperties::DashStopVector& rCustomDash)
92
0
{
93
0
    OSL_ASSERT(!rCustomDash.empty());
94
    // Assume all dash stops have the same sp values.
95
0
    orLineDash.Distance = rCustomDash[0].second;
96
    // First kind of dashes go to "Dots"
97
0
    orLineDash.DotLen = rCustomDash[0].first;
98
0
    orLineDash.Dots = 0;
99
0
    for(const auto& rIt : rCustomDash)
100
0
    {
101
0
        if (rIt.first != orLineDash.DotLen)
102
0
            break;
103
0
        ++orLineDash.Dots;
104
0
    }
105
    // All others go to "Dashes", we cannot handle more than two kinds.
106
0
    orLineDash.Dashes = rCustomDash.size() - orLineDash.Dots;
107
0
    if (orLineDash.Dashes > 0)
108
0
        orLineDash.DashLen = rCustomDash[orLineDash.Dots].first;
109
0
    else
110
0
        orLineDash.DashLen = 0;
111
112
    // convert to API, e.g. 123% is 123000 in MS Office and 123 in our API
113
0
    orLineDash.DotLen = orLineDash.DotLen / 1000;
114
0
    orLineDash.DashLen = orLineDash.DashLen / 1000;
115
0
    orLineDash.Distance = orLineDash.Distance / 1000;
116
0
}
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
 */
123
void lclRecoverStandardDashStyles(LineDash& orLineDash, sal_Int32 nLineWidth)
124
0
{
125
0
    sal_uInt16 nDots = orLineDash.Dots;
126
0
    sal_uInt16 nDashes = orLineDash.Dashes;
127
0
    sal_uInt32 nDotLen = orLineDash.DotLen;
128
0
    sal_uInt32 nDashLen = orLineDash.DashLen;
129
0
    sal_uInt32 nDistance = orLineDash.Distance;
130
    // Use same ersatz for hairline as in export.
131
0
    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
0
    if (nDots == 3 && nDotLen == 197 &&nDashes == 3 && nDashLen == 100 && nDistance == 100)
135
0
    {   // "3 Dashes 3 Dots (var)"
136
0
        orLineDash.DashLen = 0;
137
0
    }
138
0
    else if (nDots == 1 && nDotLen == 100 && nDashes == 0 && nDistance == 50)
139
0
    {   // "Ultrafine Dotted (var)"
140
0
        orLineDash.DotLen = 0;
141
0
    }
142
0
    else if (nDots == 2 && nDashes == 0 && nDotLen == nDistance
143
0
        && std::abs(nDistance * fWidthHelp - 51.0) < fWidthHelp)
144
0
    {   // "Ultrafine Dashed"
145
0
        orLineDash.Dots = 1;
146
0
        orLineDash.DotLen = 51;
147
0
        orLineDash.Dashes = 1;
148
0
        orLineDash.DashLen = 51;
149
0
        orLineDash.Distance = 51;
150
0
        orLineDash.Style = orLineDash.Style == DashStyle_ROUNDRELATIVE ? DashStyle_ROUND : DashStyle_RECT;
151
0
    }
152
0
    else if (nDots == 2 && nDashes == 3 && std::abs(nDotLen * fWidthHelp - 51.0) < fWidthHelp
153
0
        && std::abs(nDashLen * fWidthHelp - 254.0) < fWidthHelp
154
0
        && std::abs(nDistance * fWidthHelp - 127.0) < fWidthHelp)
155
0
    {   // "Ultrafine 2 Dots 3 Dashes"
156
0
        orLineDash.DotLen = 51;
157
0
        orLineDash.DashLen = 254;
158
0
        orLineDash.Distance = 127;
159
0
        orLineDash.Style = orLineDash.Style == DashStyle_ROUNDRELATIVE ? DashStyle_ROUND : DashStyle_RECT;
160
0
    }
161
0
    else if (nDots == 1 && nDotLen == 100 && nDashes == 0
162
0
        && std::abs(nDistance * fWidthHelp - 457.0) < fWidthHelp)
163
0
    {    // "Fine Dotted"
164
0
        orLineDash.DotLen = 0;
165
0
        orLineDash.Distance = 457;
166
0
        orLineDash.Style = orLineDash.Style == DashStyle_ROUNDRELATIVE ? DashStyle_ROUND : DashStyle_RECT;
167
0
    }
168
0
    else if (nDots == 1 && nDashes == 10 && nDashLen == 100
169
0
        && std::abs(nDistance * fWidthHelp - 152.0) < fWidthHelp)
170
0
    {   // "Line with Fine Dots"
171
0
        orLineDash.DotLen = 2007;
172
0
        orLineDash.DashLen = 0;
173
0
        orLineDash.Distance = 152;
174
0
        orLineDash.Style = orLineDash.Style == DashStyle_ROUNDRELATIVE ? DashStyle_ROUND : DashStyle_RECT;
175
0
    }
176
0
    else if (nDots == 2 && nDotLen == 100 && nDashes == 1 && nDashLen == nDistance
177
0
        && std::abs(nDistance * fWidthHelp - 203.0) < fWidthHelp)
178
0
    {   // "2 Dots 1 Dash"
179
0
        orLineDash.DotLen = 0;
180
0
        orLineDash.DashLen = 203;
181
0
        orLineDash.Distance = 203;
182
0
        orLineDash.Style = orLineDash.Style == DashStyle_ROUNDRELATIVE ? DashStyle_ROUND : DashStyle_RECT;
183
0
    }
184
0
}
185
186
DashStyle lclGetDashStyle( sal_Int32 nToken )
187
124
{
188
124
    OSL_ASSERT((nToken & sal_Int32(0xFFFF0000))==0);
189
    // MS Office dashing is always relative to line width
190
124
    switch( nToken )
191
124
    {
192
0
        case XML_rnd:   return DashStyle_ROUNDRELATIVE;
193
0
        case XML_sq:    return DashStyle_RECTRELATIVE; // default in OOXML
194
124
        case XML_flat:  return DashStyle_RECTRELATIVE; // default in MS Office
195
124
    }
196
0
    return DashStyle_RECTRELATIVE;
197
124
}
198
199
LineCap lclGetLineCap( sal_Int32 nToken )
200
11.2k
{
201
11.2k
    OSL_ASSERT((nToken & sal_Int32(0xFFFF0000))==0);
202
11.2k
    switch( nToken )
203
11.2k
    {
204
0
        case XML_rnd:   return LineCap_ROUND;
205
0
        case XML_sq:    return LineCap_SQUARE; // default in OOXML
206
11.2k
        case XML_flat:  return LineCap_BUTT; // default in MS Office
207
11.2k
    }
208
0
    return LineCap_BUTT;
209
11.2k
}
210
211
LineJoint lclGetLineJoint( sal_Int32 nToken )
212
15.1k
{
213
15.1k
    OSL_ASSERT((nToken & sal_Int32(0xFFFF0000))==0);
214
15.1k
    switch( nToken )
215
15.1k
    {
216
9.93k
        case XML_round: return LineJoint_ROUND;
217
5
        case XML_bevel: return LineJoint_BEVEL;
218
5.22k
        case XML_miter: return LineJoint_MITER;
219
15.1k
    }
220
0
    return LineJoint_ROUND;
221
15.1k
}
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
227
sal_Int32 lclGetArrowSize( sal_Int32 nToken )
228
1.94k
{
229
1.94k
    OSL_ASSERT((nToken & sal_Int32(0xFFFF0000))==0);
230
1.94k
    switch( nToken )
231
1.94k
    {
232
128
        case XML_sm:    return OOX_ARROWSIZE_SMALL;
233
1.81k
        case XML_med:   return OOX_ARROWSIZE_MEDIUM;
234
0
        case XML_lg:    return OOX_ARROWSIZE_LARGE;
235
1.94k
    }
236
0
    return OOX_ARROWSIZE_MEDIUM;
237
1.94k
}
238
239
void lclPushMarkerProperties( ShapePropertyMap& rPropMap,
240
        const LineArrowProperties& rArrowProps, sal_Int32 nLineWidth, bool bLineEnd )
241
179k
{
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
179k
    NamedValue aNamedMarker;
245
246
179k
    OUStringBuffer aBuffer;
247
179k
    sal_Int32 nMarkerWidth = 0;
248
179k
    bool bMarkerCenter = false;
249
179k
    sal_Int32 nArrowType = rArrowProps.moArrowType.value_or( XML_none );
250
179k
    OSL_ASSERT((nArrowType & sal_Int32(0xFFFF0000))==0);
251
179k
    switch( nArrowType )
252
179k
    {
253
358
        case XML_triangle:
254
358
            aBuffer.append( "msArrowEnd" );
255
358
        break;
256
610
        case XML_arrow:
257
610
            aBuffer.append( "msArrowOpenEnd" );
258
610
        break;
259
3
        case XML_stealth:
260
3
            aBuffer.append( "msArrowStealthEnd" );
261
3
        break;
262
0
        case XML_diamond:
263
0
            aBuffer.append( "msArrowDiamondEnd" );
264
0
            bMarkerCenter = true;
265
0
        break;
266
0
        case XML_oval:
267
0
            aBuffer.append( "msArrowOvalEnd" );
268
0
            bMarkerCenter = true;
269
0
        break;
270
179k
    }
271
272
179k
    if( !aBuffer.isEmpty() )
273
971
    {
274
971
        bool bIsArrow = nArrowType == XML_arrow;
275
971
        sal_Int32 nLength = lclGetArrowSize( rArrowProps.moArrowLength.value_or( XML_med ) );
276
971
        sal_Int32 nWidth  = lclGetArrowSize( rArrowProps.moArrowWidth.value_or( XML_med ) );
277
278
971
        sal_Int32 nNameIndex = nWidth * 3 + nLength + 1;
279
971
        aBuffer.append( " " + OUString::number( nNameIndex ));
280
971
        if (bIsArrow)
281
610
        {
282
            // Arrow marker form depends also on line width
283
610
            aBuffer.append(" " + OUString::number(nLineWidth));
284
610
        }
285
971
        OUString aMarkerName = aBuffer.makeStringAndClear();
286
287
971
        double fArrowLength = 1.0;
288
971
        switch( nLength )
289
971
        {
290
64
            case OOX_ARROWSIZE_SMALL:   fArrowLength = (bIsArrow ? 2.5 : 2.0); break;
291
907
            case OOX_ARROWSIZE_MEDIUM:  fArrowLength = (bIsArrow ? 3.5 : 3.0); break;
292
0
            case OOX_ARROWSIZE_LARGE:   fArrowLength = (bIsArrow ? 5.5 : 5.0); break;
293
971
        }
294
971
        double fArrowWidth = 1.0;
295
971
        switch( nWidth )
296
971
        {
297
64
            case OOX_ARROWSIZE_SMALL:   fArrowWidth = (bIsArrow ? 2.5 : 2.0);  break;
298
907
            case OOX_ARROWSIZE_MEDIUM:  fArrowWidth = (bIsArrow ? 3.5 : 3.0);  break;
299
0
            case OOX_ARROWSIZE_LARGE:   fArrowWidth = (bIsArrow ? 5.5 : 5.0);  break;
300
971
        }
301
        // set arrow width relative to line width
302
971
        sal_Int32 nBaseLineWidth = ::std::max< sal_Int32 >( nLineWidth, 70 );
303
971
        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
971
        if( !rPropMap.hasNamedLineMarkerInTable( aMarkerName ) )
310
270
        {
311
            // pass X and Y as percentage to OOX_ARROW_POINT
312
1.82k
            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
270
            const double fArrowLineHalfWidth = ::std::max< double >( 100.0 * 0.5 * nLineWidth / nMarkerWidth, 1 );
317
318
270
            ::std::vector< awt::Point > aPoints;
319
270
            OSL_ASSERT((rArrowProps.moArrowType.value() & sal_Int32(0xFFFF0000))==0);
320
270
            switch( rArrowProps.moArrowType.value() )
321
270
            {
322
143
                case XML_triangle:
323
143
                    aPoints.push_back( OOX_ARROW_POINT(  50,   0 ) );
324
143
                    aPoints.push_back( OOX_ARROW_POINT( 100, 100 ) );
325
143
                    aPoints.push_back( OOX_ARROW_POINT(   0, 100 ) );
326
143
                    aPoints.push_back( OOX_ARROW_POINT(  50,   0 ) );
327
143
                break;
328
124
                case XML_arrow:
329
124
                    aPoints.push_back( OOX_ARROW_POINT( 50, 0 ) );
330
124
                    aPoints.push_back( OOX_ARROW_POINT( 100, 100 - fArrowLineHalfWidth * 1.5) );
331
124
                    aPoints.push_back( OOX_ARROW_POINT( 100 - fArrowLineHalfWidth * 1.5, 100 ) );
332
124
                    aPoints.push_back( OOX_ARROW_POINT( 50.0 + fArrowLineHalfWidth, 5.5 * fArrowLineHalfWidth) );
333
124
                    aPoints.push_back( OOX_ARROW_POINT( 50.0 + fArrowLineHalfWidth, 100 ) );
334
124
                    aPoints.push_back( OOX_ARROW_POINT( 50.0 - fArrowLineHalfWidth, 100 ) );
335
124
                    aPoints.push_back( OOX_ARROW_POINT( 50.0 - fArrowLineHalfWidth, 5.5 * fArrowLineHalfWidth) );
336
124
                    aPoints.push_back( OOX_ARROW_POINT( fArrowLineHalfWidth * 1.5, 100 ) );
337
124
                    aPoints.push_back( OOX_ARROW_POINT( 0, 100 - fArrowLineHalfWidth * 1.5) );
338
124
                    aPoints.push_back( OOX_ARROW_POINT( 50, 0 ) );
339
124
                break;
340
3
                case XML_stealth:
341
3
                    aPoints.push_back( OOX_ARROW_POINT(  50,   0 ) );
342
3
                    aPoints.push_back( OOX_ARROW_POINT( 100, 100 ) );
343
3
                    aPoints.push_back( OOX_ARROW_POINT(  50,  60 ) );
344
3
                    aPoints.push_back( OOX_ARROW_POINT(   0, 100 ) );
345
3
                    aPoints.push_back( OOX_ARROW_POINT(  50,   0 ) );
346
3
                break;
347
0
                case XML_diamond:
348
0
                    aPoints.push_back( OOX_ARROW_POINT(  50,   0 ) );
349
0
                    aPoints.push_back( OOX_ARROW_POINT( 100,  50 ) );
350
0
                    aPoints.push_back( OOX_ARROW_POINT(  50, 100 ) );
351
0
                    aPoints.push_back( OOX_ARROW_POINT(   0,  50 ) );
352
0
                    aPoints.push_back( OOX_ARROW_POINT(  50,   0 ) );
353
0
                break;
354
0
                case XML_oval:
355
0
                    aPoints.push_back( OOX_ARROW_POINT(  50,   0 ) );
356
0
                    aPoints.push_back( OOX_ARROW_POINT(  75,   7 ) );
357
0
                    aPoints.push_back( OOX_ARROW_POINT(  93,  25 ) );
358
0
                    aPoints.push_back( OOX_ARROW_POINT( 100,  50 ) );
359
0
                    aPoints.push_back( OOX_ARROW_POINT(  93,  75 ) );
360
0
                    aPoints.push_back( OOX_ARROW_POINT(  75,  93 ) );
361
0
                    aPoints.push_back( OOX_ARROW_POINT(  50, 100 ) );
362
0
                    aPoints.push_back( OOX_ARROW_POINT(  25,  93 ) );
363
0
                    aPoints.push_back( OOX_ARROW_POINT(   7,  75 ) );
364
0
                    aPoints.push_back( OOX_ARROW_POINT(   0,  50 ) );
365
0
                    aPoints.push_back( OOX_ARROW_POINT(   7,  25 ) );
366
0
                    aPoints.push_back( OOX_ARROW_POINT(  25,   7 ) );
367
0
                    aPoints.push_back( OOX_ARROW_POINT(  50,   0 ) );
368
0
                break;
369
270
            }
370
371
270
            OSL_ENSURE( !aPoints.empty(), "lclPushMarkerProperties - missing arrow coordinates" );
372
270
            if( !aPoints.empty() )
373
270
            {
374
270
                PolyPolygonBezierCoords aMarkerCoords;
375
270
                aMarkerCoords.Coordinates = { comphelper::containerToSequence( aPoints ) };
376
377
270
                ::std::vector< PolygonFlags > aFlags( aPoints.size(), PolygonFlags_NORMAL );
378
270
                aMarkerCoords.Flags = { comphelper::containerToSequence( aFlags ) };
379
380
270
                aNamedMarker.Name = aMarkerName;
381
270
                aNamedMarker.Value <<= aMarkerCoords;
382
270
            }
383
270
        }
384
701
        else
385
701
        {
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
701
            aNamedMarker.Name = aMarkerName;
390
701
        }
391
971
    }
392
393
    // push the properties (filled aNamedMarker.Name indicates valid marker)
394
179k
    if( aNamedMarker.Name.isEmpty() )
395
178k
        return;
396
397
971
    if( bLineEnd )
398
746
    {
399
746
        rPropMap.setProperty( ShapeProperty::LineEnd, aNamedMarker );
400
746
        rPropMap.setProperty( ShapeProperty::LineEndWidth, nMarkerWidth );
401
746
        rPropMap.setProperty( ShapeProperty::LineEndCenter, bMarkerCenter );
402
746
    }
403
225
    else
404
225
    {
405
225
        rPropMap.setProperty( ShapeProperty::LineStart, aNamedMarker );
406
225
        rPropMap.setProperty( ShapeProperty::LineStartWidth, nMarkerWidth );
407
225
        rPropMap.setProperty( ShapeProperty::LineStartCenter, bMarkerCenter );
408
225
    }
409
971
}
410
411
} // namespace
412
413
void LineArrowProperties::assignUsed( const LineArrowProperties& rSourceProps )
414
544k
{
415
544k
    assignIfUsed( moArrowType, rSourceProps.moArrowType );
416
544k
    assignIfUsed( moArrowWidth, rSourceProps.moArrowWidth );
417
544k
    assignIfUsed( moArrowLength, rSourceProps.moArrowLength );
418
544k
}
419
420
void LineProperties::assignUsed( const LineProperties& rSourceProps )
421
272k
{
422
272k
    maStartArrow.assignUsed( rSourceProps.maStartArrow );
423
272k
    maEndArrow.assignUsed( rSourceProps.maEndArrow );
424
272k
    maLineFill.assignUsed( rSourceProps.maLineFill );
425
272k
    if( !rSourceProps.maCustomDash.empty() )
426
0
        maCustomDash = rSourceProps.maCustomDash;
427
272k
    assignIfUsed( moLineWidth, rSourceProps.moLineWidth );
428
272k
    assignIfUsed( moPresetDash, rSourceProps.moPresetDash );
429
272k
    assignIfUsed( moLineCompound, rSourceProps.moLineCompound );
430
272k
    assignIfUsed( moLineCap, rSourceProps.moLineCap );
431
272k
    assignIfUsed( moLineJoint, rSourceProps.moLineJoint );
432
272k
}
433
434
void LineProperties::pushToPropMap( ShapePropertyMap& rPropMap,
435
        const GraphicHelper& rGraphicHelper, ::Color nPhClr, sal_Int16 nPhClrTheme) const
436
89.8k
{
437
    // line fill type must exist, otherwise ignore other properties
438
89.8k
    if( !maLineFill.moFillType.has_value() )
439
0
        return;
440
441
    // line style (our core only supports none and solid)
442
89.8k
    drawing::LineStyle eLineStyle = (maLineFill.moFillType.value() == XML_noFill) ? drawing::LineStyle_NONE : drawing::LineStyle_SOLID;
443
444
    // line width in 1/100mm
445
89.8k
    sal_Int32 nLineWidth = getLineWidth(); // includes conversion from EMUs to 1/100mm
446
89.8k
    rPropMap.setProperty( ShapeProperty::LineWidth, nLineWidth );
447
448
    // line cap type
449
89.8k
    LineCap eLineCap = moLineCap.has_value() ? lclGetLineCap( moLineCap.value() ) : LineCap_BUTT;
450
89.8k
    if( moLineCap.has_value() )
451
9.82k
        rPropMap.setProperty( ShapeProperty::LineCap, eLineCap );
452
453
    // create line dash from preset dash token or dash stop vector (not for invisible line)
454
89.8k
    if( (eLineStyle != drawing::LineStyle_NONE) &&
455
89.8k
        ((moPresetDash.has_value() && moPresetDash.value() != XML_solid) || !maCustomDash.empty()) )
456
124
    {
457
124
        LineDash aLineDash;
458
124
        aLineDash.Style = lclGetDashStyle( moLineCap.value_or( XML_flat ) );
459
460
124
        if(moPresetDash.has_value() && moPresetDash.value() != XML_solid)
461
124
            lclConvertPresetDash(aLineDash, moPresetDash.value_or(XML_dash));
462
0
        else // !maCustomDash.empty()
463
0
        {
464
0
            lclConvertCustomDash(aLineDash, maCustomDash);
465
0
            lclRecoverStandardDashStyles(aLineDash, nLineWidth);
466
0
        }
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
124
        if (eLineCap == LineCap_ROUND || (eLineCap == LineCap_SQUARE && maCustomDash.empty()))
472
0
        {
473
            // Cannot use -100 because that results in 0 length in some cases and
474
            // LibreOffice interprets 0 length as 100%.
475
0
            if (aLineDash.DotLen >= 100 || aLineDash.DashLen >= 100)
476
0
                aLineDash.Distance += 99;
477
0
            if (aLineDash.DotLen >= 100)
478
0
                aLineDash.DotLen -= 99;
479
0
            if (aLineDash.DashLen >= 100)
480
0
                aLineDash.DashLen -= 99;
481
0
        }
482
483
124
        if( rPropMap.setProperty( ShapeProperty::LineDash, aLineDash ) )
484
124
            eLineStyle = drawing::LineStyle_DASH;
485
124
    }
486
487
    // set final line style property
488
89.8k
    rPropMap.setProperty( ShapeProperty::LineStyle, eLineStyle );
489
490
    // line joint type
491
89.8k
    if( moLineJoint.has_value() )
492
14.9k
        rPropMap.setProperty( ShapeProperty::LineJoint, lclGetLineJoint( moLineJoint.value() ) );
493
494
    // line color and transparence
495
89.8k
    Color aLineColor = maLineFill.getBestSolidColor();
496
89.8k
    if (aLineColor.isUsed())
497
15.6k
    {
498
15.6k
        ::Color aColor = aLineColor.getColor(rGraphicHelper, nPhClr);
499
15.6k
        rPropMap.setProperty(ShapeProperty::LineColor, aColor);
500
15.6k
        if( aLineColor.hasTransparency() )
501
0
            rPropMap.setProperty( ShapeProperty::LineTransparency, aLineColor.getTransparency() );
502
503
15.6k
        model::ComplexColor aComplexColor;
504
505
15.6k
        if (aColor == nPhClr)
506
955
        {
507
955
            aComplexColor.setThemeColor(model::convertToThemeColorType(nPhClrTheme));
508
955
            rPropMap.setProperty(PROP_LineComplexColor, model::color::createXComplexColor(aComplexColor));
509
955
        }
510
14.6k
        else
511
14.6k
        {
512
14.6k
            aComplexColor.setThemeColor(model::convertToThemeColorType(aLineColor.getSchemeColorIndex()));
513
14.6k
            if (aLineColor.getLumMod() != 10000)
514
130
                aComplexColor.addTransformation({model::TransformationType::LumMod, aLineColor.getLumMod()});
515
14.6k
            if (aLineColor.getLumOff() != 0)
516
127
                aComplexColor.addTransformation({model::TransformationType::LumOff, aLineColor.getLumOff()});
517
14.6k
            if (aLineColor.getTintOrShade() > 0)
518
20
                aComplexColor.addTransformation({model::TransformationType::Tint, aLineColor.getTintOrShade()});
519
14.6k
            if (aLineColor.getTintOrShade() < 0)
520
299
            {
521
299
                sal_Int16 nShade = o3tl::narrowing<sal_Int16>(-aLineColor.getTintOrShade());
522
299
                aComplexColor.addTransformation({model::TransformationType::Shade, nShade});
523
299
            }
524
14.6k
            rPropMap.setProperty(PROP_LineComplexColor, model::color::createXComplexColor(aComplexColor));
525
14.6k
        }
526
15.6k
    }
527
528
    // line markers
529
89.8k
    lclPushMarkerProperties( rPropMap, maStartArrow, nLineWidth, false );
530
89.8k
    lclPushMarkerProperties( rPropMap, maEndArrow,   nLineWidth, true );
531
89.8k
}
532
533
drawing::LineStyle LineProperties::getLineStyle() const
534
3.91k
{
535
    // rules to calculate the line style inferred from the code in LineProperties::pushToPropMap
536
3.91k
    if (maLineFill.moFillType.value() == XML_noFill)
537
2.45k
        return drawing::LineStyle_NONE;
538
1.45k
    if ((moPresetDash.has_value() && moPresetDash.value() != XML_solid) ||
539
1.45k
        (!moPresetDash && !maCustomDash.empty()))
540
0
       return drawing::LineStyle_DASH;
541
1.45k
    return drawing::LineStyle_SOLID;
542
1.45k
}
543
544
drawing::LineCap LineProperties::getLineCap() const
545
3.91k
{
546
3.91k
    if( moLineCap.has_value() )
547
1.45k
        return lclGetLineCap( moLineCap.value() );
548
549
2.45k
    return drawing::LineCap_BUTT;
550
3.91k
}
551
552
drawing::LineJoint LineProperties::getLineJoint() const
553
3.91k
{
554
3.91k
    if( moLineJoint.has_value() )
555
205
        return lclGetLineJoint( moLineJoint.value() );
556
557
3.71k
    return drawing::LineJoint_NONE;
558
3.91k
}
559
560
sal_Int32 LineProperties::getLineWidth() const
561
93.7k
{
562
93.7k
    return convertEmuToHmm( moLineWidth.value_or( 0 ) );
563
93.7k
}
564
565
} // namespace oox
566
567
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */