Line data Source code
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 "oox/drawingml/lineproperties.hxx"
21 : #include <vector>
22 : #include <rtl/ustrbuf.hxx>
23 : #include <osl/diagnose.h>
24 : #include <com/sun/star/beans/NamedValue.hpp>
25 : #include <com/sun/star/container/XNameContainer.hpp>
26 : #include <com/sun/star/drawing/FlagSequence.hpp>
27 : #include <com/sun/star/drawing/LineDash.hpp>
28 : #include <com/sun/star/drawing/LineJoint.hpp>
29 : #include <com/sun/star/drawing/LineStyle.hpp>
30 : #include <com/sun/star/drawing/PointSequence.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/containerhelper.hxx"
35 : #include "oox/helper/graphichelper.hxx"
36 : #include "oox/token/tokens.hxx"
37 :
38 : using namespace ::com::sun::star;
39 : using namespace ::com::sun::star::beans;
40 : using namespace ::com::sun::star::drawing;
41 :
42 : using ::com::sun::star::uno::Any;
43 : using ::com::sun::star::uno::Reference;
44 : using ::com::sun::star::container::XNameContainer;
45 :
46 : namespace oox {
47 : namespace drawingml {
48 :
49 : namespace {
50 :
51 38 : void lclSetDashData( LineDash& orLineDash, sal_Int16 nDots, sal_Int32 nDotLen,
52 : sal_Int16 nDashes, sal_Int32 nDashLen, sal_Int32 nDistance )
53 : {
54 38 : orLineDash.Dots = nDots;
55 38 : orLineDash.DotLen = nDotLen;
56 38 : orLineDash.Dashes = nDashes;
57 38 : orLineDash.DashLen = nDashLen;
58 38 : orLineDash.Distance = nDistance;
59 38 : }
60 :
61 : /** Converts the specified preset dash to API dash.
62 :
63 : Line length and dot length are set relative to line width and have to be
64 : multiplied by the actual line width after this function.
65 : */
66 38 : void lclConvertPresetDash( LineDash& orLineDash, sal_Int32 nPresetDash )
67 : {
68 38 : switch( nPresetDash )
69 : {
70 0 : case XML_dot: lclSetDashData( orLineDash, 1, 1, 0, 0, 3 ); break;
71 22 : case XML_dash: lclSetDashData( orLineDash, 0, 0, 1, 4, 3 ); break;
72 0 : case XML_dashDot: lclSetDashData( orLineDash, 1, 1, 1, 4, 3 ); break;
73 :
74 6 : case XML_lgDash: lclSetDashData( orLineDash, 0, 0, 1, 8, 3 ); break;
75 0 : case XML_lgDashDot: lclSetDashData( orLineDash, 1, 1, 1, 8, 3 ); break;
76 2 : case XML_lgDashDotDot: lclSetDashData( orLineDash, 2, 1, 1, 8, 3 ); break;
77 :
78 6 : case XML_sysDot: lclSetDashData( orLineDash, 1, 1, 0, 0, 1 ); break;
79 2 : case XML_sysDash: lclSetDashData( orLineDash, 0, 0, 1, 3, 1 ); break;
80 0 : case XML_sysDashDot: lclSetDashData( orLineDash, 1, 1, 1, 3, 1 ); break;
81 0 : case XML_sysDashDotDot: lclSetDashData( orLineDash, 2, 1, 1, 3, 1 ); break;
82 :
83 : default:
84 : OSL_FAIL( "lclConvertPresetDash - unsupported preset dash" );
85 0 : lclSetDashData( orLineDash, 0, 0, 1, 4, 3 );
86 : }
87 38 : }
88 :
89 : /** Converts the passed custom dash to API dash.
90 :
91 : Line length and dot length are set relative to line width and have to be
92 : multiplied by the actual line width after this function.
93 : */
94 23 : void lclConvertCustomDash( LineDash& orLineDash, const LineProperties::DashStopVector& rCustomDash )
95 : {
96 23 : if( rCustomDash.empty() )
97 : {
98 : OSL_FAIL( "lclConvertCustomDash - unexpected empty custom dash" );
99 0 : lclSetDashData( orLineDash, 0, 0, 1, 4, 3 );
100 23 : return;
101 : }
102 :
103 : // count dashes and dots (stops equal or less than 2 are assumed to be dots)
104 23 : sal_Int16 nDots = 0;
105 23 : sal_Int32 nDotLen = 0;
106 23 : sal_Int16 nDashes = 0;
107 23 : sal_Int32 nDashLen = 0;
108 23 : sal_Int32 nDistance = 0;
109 23 : sal_Int32 nConvertedLen = 0;
110 23 : sal_Int32 nConvertedDistance = 0;
111 60 : for( LineProperties::DashStopVector::const_iterator aIt = rCustomDash.begin(), aEnd = rCustomDash.end(); aIt != aEnd; ++aIt )
112 : {
113 : // Get from "1000th of percent" ==> percent ==> multiplier
114 37 : nConvertedLen = aIt->first / 1000 / 100;
115 37 : nConvertedDistance = aIt->second / 1000 / 100;
116 :
117 : // Check if it is a dot (100% = dot)
118 37 : if( nConvertedLen == 1 )
119 : {
120 15 : ++nDots;
121 15 : nDotLen += nConvertedLen;
122 : }
123 : else
124 : {
125 22 : ++nDashes;
126 22 : nDashLen += nConvertedLen;
127 : }
128 37 : nDistance += nConvertedDistance;
129 : }
130 23 : orLineDash.DotLen = (nDots > 0) ? ::std::max< sal_Int32 >( nDotLen / nDots, 1 ) : 0;
131 23 : orLineDash.Dots = nDots;
132 23 : orLineDash.DashLen = (nDashes > 0) ? ::std::max< sal_Int32 >( nDashLen / nDashes, 1 ) : 0;
133 23 : orLineDash.Dashes = nDashes;
134 23 : orLineDash.Distance = ::std::max< sal_Int32 >( nDistance / rCustomDash.size(), 1 );
135 : }
136 :
137 61 : DashStyle lclGetDashStyle( sal_Int32 nToken )
138 : {
139 : OSL_ASSERT((nToken & sal_Int32(0xFFFF0000))==0);
140 61 : switch( nToken )
141 : {
142 33 : case XML_rnd: return DashStyle_ROUNDRELATIVE;
143 0 : case XML_sq: return DashStyle_RECTRELATIVE;
144 28 : case XML_flat: return DashStyle_RECT;
145 : }
146 0 : return DashStyle_ROUNDRELATIVE;
147 : }
148 :
149 1665 : LineJoint lclGetLineJoint( sal_Int32 nToken )
150 : {
151 : OSL_ASSERT((nToken & sal_Int32(0xFFFF0000))==0);
152 1665 : switch( nToken )
153 : {
154 893 : case XML_round: return LineJoint_ROUND;
155 85 : case XML_bevel: return LineJoint_BEVEL;
156 687 : case XML_miter: return LineJoint_MITER;
157 : }
158 0 : return LineJoint_ROUND;
159 : }
160 :
161 : const sal_Int32 OOX_ARROWSIZE_SMALL = 0;
162 : const sal_Int32 OOX_ARROWSIZE_MEDIUM = 1;
163 : const sal_Int32 OOX_ARROWSIZE_LARGE = 2;
164 :
165 252 : sal_Int32 lclGetArrowSize( sal_Int32 nToken )
166 : {
167 : OSL_ASSERT((nToken & sal_Int32(0xFFFF0000))==0);
168 252 : switch( nToken )
169 : {
170 8 : case XML_sm: return OOX_ARROWSIZE_SMALL;
171 244 : case XML_med: return OOX_ARROWSIZE_MEDIUM;
172 0 : case XML_lg: return OOX_ARROWSIZE_LARGE;
173 : }
174 0 : return OOX_ARROWSIZE_MEDIUM;
175 : }
176 :
177 10870 : void lclPushMarkerProperties( ShapePropertyMap& rPropMap,
178 : const LineArrowProperties& rArrowProps, sal_Int32 nLineWidth, bool bLineEnd )
179 : {
180 : /* Store the marker polygon and the marker name in a single value, to be
181 : able to pass both to the ShapePropertyMap::setProperty() function. */
182 10870 : NamedValue aNamedMarker;
183 :
184 21740 : OUStringBuffer aBuffer;
185 10870 : sal_Int32 nMarkerWidth = 0;
186 10870 : bool bMarkerCenter = false;
187 10870 : sal_Int32 nArrowType = rArrowProps.moArrowType.get( XML_none );
188 : OSL_ASSERT((nArrowType & sal_Int32(0xFFFF0000))==0);
189 10870 : switch( nArrowType )
190 : {
191 : case XML_triangle:
192 93 : aBuffer.append( "msArrowEnd" );
193 93 : break;
194 : case XML_arrow:
195 17 : aBuffer.append( "msArrowOpenEnd" );
196 17 : break;
197 : case XML_stealth:
198 4 : aBuffer.append( "msArrowStealthEnd" );
199 4 : break;
200 : case XML_diamond:
201 12 : aBuffer.append( "msArrowDiamondEnd" );
202 12 : bMarkerCenter = true;
203 12 : break;
204 : case XML_oval:
205 0 : aBuffer.append( "msArrowOvalEnd" );
206 0 : bMarkerCenter = true;
207 0 : break;
208 : }
209 :
210 10870 : if( !aBuffer.isEmpty() )
211 : {
212 126 : sal_Int32 nLength = lclGetArrowSize( rArrowProps.moArrowLength.get( XML_med ) );
213 126 : sal_Int32 nWidth = lclGetArrowSize( rArrowProps.moArrowWidth.get( XML_med ) );
214 :
215 126 : sal_Int32 nNameIndex = nWidth * 3 + nLength + 1;
216 126 : aBuffer.append( ' ' ).append( nNameIndex );
217 126 : OUString aMarkerName = aBuffer.makeStringAndClear();
218 :
219 126 : bool bIsArrow = nArrowType == XML_arrow;
220 126 : double fArrowLength = 1.0;
221 126 : switch( nLength )
222 : {
223 4 : case OOX_ARROWSIZE_SMALL: fArrowLength = (bIsArrow ? 3.5 : 2.0); break;
224 122 : case OOX_ARROWSIZE_MEDIUM: fArrowLength = (bIsArrow ? 4.5 : 3.0); break;
225 0 : case OOX_ARROWSIZE_LARGE: fArrowLength = (bIsArrow ? 6.0 : 5.0); break;
226 : }
227 126 : double fArrowWidth = 1.0;
228 126 : switch( nWidth )
229 : {
230 4 : case OOX_ARROWSIZE_SMALL: fArrowWidth = (bIsArrow ? 3.5 : 2.0); break;
231 122 : case OOX_ARROWSIZE_MEDIUM: fArrowWidth = (bIsArrow ? 4.5 : 3.0); break;
232 0 : case OOX_ARROWSIZE_LARGE: fArrowWidth = (bIsArrow ? 6.0 : 5.0); break;
233 : }
234 : // set arrow width relative to line width
235 126 : sal_Int32 nBaseLineWidth = ::std::max< sal_Int32 >( nLineWidth, 70 );
236 126 : nMarkerWidth = static_cast< sal_Int32 >( fArrowWidth * nBaseLineWidth );
237 :
238 : /* Test if the marker already exists in the marker table, do not
239 : create it again in this case. If markers are inserted explicitly
240 : instead by their name, the polygon will be created always.
241 : TODO: this can be optimized by using a map. */
242 126 : if( !rPropMap.hasNamedLineMarkerInTable( aMarkerName ) )
243 : {
244 : // pass X and Y as percentage to OOX_ARROW_POINT
245 : #define OOX_ARROW_POINT( x, y ) awt::Point( static_cast< sal_Int32 >( fArrowWidth * x ), static_cast< sal_Int32 >( fArrowLength * y ) )
246 :
247 34 : ::std::vector< awt::Point > aPoints;
248 : OSL_ASSERT((rArrowProps.moArrowType.get() & sal_Int32(0xFFFF0000))==0);
249 34 : switch( rArrowProps.moArrowType.get() )
250 : {
251 : case XML_triangle:
252 17 : aPoints.push_back( OOX_ARROW_POINT( 50, 0 ) );
253 17 : aPoints.push_back( OOX_ARROW_POINT( 100, 100 ) );
254 17 : aPoints.push_back( OOX_ARROW_POINT( 0, 100 ) );
255 17 : aPoints.push_back( OOX_ARROW_POINT( 50, 0 ) );
256 17 : break;
257 : case XML_arrow:
258 9 : aPoints.push_back( OOX_ARROW_POINT( 50, 0 ) );
259 9 : aPoints.push_back( OOX_ARROW_POINT( 100, 91 ) );
260 9 : aPoints.push_back( OOX_ARROW_POINT( 85, 100 ) );
261 9 : aPoints.push_back( OOX_ARROW_POINT( 50, 36 ) );
262 9 : aPoints.push_back( OOX_ARROW_POINT( 15, 100 ) );
263 9 : aPoints.push_back( OOX_ARROW_POINT( 0, 91 ) );
264 9 : aPoints.push_back( OOX_ARROW_POINT( 50, 0 ) );
265 9 : break;
266 : case XML_stealth:
267 4 : aPoints.push_back( OOX_ARROW_POINT( 50, 0 ) );
268 4 : aPoints.push_back( OOX_ARROW_POINT( 100, 100 ) );
269 4 : aPoints.push_back( OOX_ARROW_POINT( 50, 60 ) );
270 4 : aPoints.push_back( OOX_ARROW_POINT( 0, 100 ) );
271 4 : aPoints.push_back( OOX_ARROW_POINT( 50, 0 ) );
272 4 : break;
273 : case XML_diamond:
274 4 : aPoints.push_back( OOX_ARROW_POINT( 50, 0 ) );
275 4 : aPoints.push_back( OOX_ARROW_POINT( 100, 50 ) );
276 4 : aPoints.push_back( OOX_ARROW_POINT( 50, 100 ) );
277 4 : aPoints.push_back( OOX_ARROW_POINT( 0, 50 ) );
278 4 : aPoints.push_back( OOX_ARROW_POINT( 50, 0 ) );
279 4 : break;
280 : case XML_oval:
281 0 : aPoints.push_back( OOX_ARROW_POINT( 50, 0 ) );
282 0 : aPoints.push_back( OOX_ARROW_POINT( 75, 7 ) );
283 0 : aPoints.push_back( OOX_ARROW_POINT( 93, 25 ) );
284 0 : aPoints.push_back( OOX_ARROW_POINT( 100, 50 ) );
285 0 : aPoints.push_back( OOX_ARROW_POINT( 93, 75 ) );
286 0 : aPoints.push_back( OOX_ARROW_POINT( 75, 93 ) );
287 0 : aPoints.push_back( OOX_ARROW_POINT( 50, 100 ) );
288 0 : aPoints.push_back( OOX_ARROW_POINT( 25, 93 ) );
289 0 : aPoints.push_back( OOX_ARROW_POINT( 7, 75 ) );
290 0 : aPoints.push_back( OOX_ARROW_POINT( 0, 50 ) );
291 0 : aPoints.push_back( OOX_ARROW_POINT( 7, 25 ) );
292 0 : aPoints.push_back( OOX_ARROW_POINT( 25, 7 ) );
293 0 : aPoints.push_back( OOX_ARROW_POINT( 50, 0 ) );
294 0 : break;
295 : }
296 : #undef OOX_ARROW_POINT
297 :
298 : OSL_ENSURE( !aPoints.empty(), "lclPushMarkerProperties - missing arrow coordinates" );
299 34 : if( !aPoints.empty() )
300 : {
301 34 : PolyPolygonBezierCoords aMarkerCoords;
302 34 : aMarkerCoords.Coordinates.realloc( 1 );
303 34 : aMarkerCoords.Coordinates[ 0 ] = ContainerHelper::vectorToSequence( aPoints );
304 :
305 68 : ::std::vector< PolygonFlags > aFlags( aPoints.size(), PolygonFlags_NORMAL );
306 34 : aMarkerCoords.Flags.realloc( 1 );
307 34 : aMarkerCoords.Flags[ 0 ] = ContainerHelper::vectorToSequence( aFlags );
308 :
309 34 : aNamedMarker.Name = aMarkerName;
310 68 : aNamedMarker.Value <<= aMarkerCoords;
311 34 : }
312 : }
313 : else
314 : {
315 : /* Named marker object exists already in the marker table, pass
316 : its name only. This will set the name as property value, but
317 : does not create a new object in the marker table. */
318 92 : aNamedMarker.Name = aMarkerName;
319 126 : }
320 : }
321 :
322 : // push the properties (filled aNamedMarker.Name indicates valid marker)
323 10870 : if( !aNamedMarker.Name.isEmpty() )
324 : {
325 126 : if( bLineEnd )
326 : {
327 90 : rPropMap.setProperty( SHAPEPROP_LineEnd, aNamedMarker );
328 90 : rPropMap.setProperty( SHAPEPROP_LineEndWidth, nMarkerWidth );
329 90 : rPropMap.setProperty( SHAPEPROP_LineEndCenter, bMarkerCenter );
330 : }
331 : else
332 : {
333 36 : rPropMap.setProperty( SHAPEPROP_LineStart, aNamedMarker );
334 36 : rPropMap.setProperty( SHAPEPROP_LineStartWidth, nMarkerWidth );
335 36 : rPropMap.setProperty( SHAPEPROP_LineStartCenter, bMarkerCenter );
336 : }
337 10870 : }
338 10870 : }
339 :
340 : } // namespace
341 :
342 19594 : void LineArrowProperties::assignUsed( const LineArrowProperties& rSourceProps )
343 : {
344 19594 : moArrowType.assignIfUsed( rSourceProps.moArrowType );
345 19594 : moArrowWidth.assignIfUsed( rSourceProps.moArrowWidth );
346 19594 : moArrowLength.assignIfUsed( rSourceProps.moArrowLength );
347 19594 : }
348 :
349 9797 : void LineProperties::assignUsed( const LineProperties& rSourceProps )
350 : {
351 9797 : maStartArrow.assignUsed( rSourceProps.maStartArrow );
352 9797 : maEndArrow.assignUsed( rSourceProps.maEndArrow );
353 9797 : maLineFill.assignUsed( rSourceProps.maLineFill );
354 9797 : if( !rSourceProps.maCustomDash.empty() )
355 23 : maCustomDash = rSourceProps.maCustomDash;
356 9797 : moLineWidth.assignIfUsed( rSourceProps.moLineWidth );
357 9797 : moPresetDash.assignIfUsed( rSourceProps.moPresetDash );
358 9797 : moLineCompound.assignIfUsed( rSourceProps.moLineCompound );
359 9797 : moLineCap.assignIfUsed( rSourceProps.moLineCap );
360 9797 : moLineJoint.assignIfUsed( rSourceProps.moLineJoint );
361 9797 : }
362 :
363 5561 : void LineProperties::pushToPropMap( ShapePropertyMap& rPropMap,
364 : const GraphicHelper& rGraphicHelper, sal_Int32 nPhClr ) const
365 : {
366 : // line fill type must exist, otherwise ignore other properties
367 5561 : if( maLineFill.moFillType.has() )
368 : {
369 : // line style (our core only supports none and solid)
370 5435 : drawing::LineStyle eLineStyle = (maLineFill.moFillType.get() == XML_noFill) ? drawing::LineStyle_NONE : drawing::LineStyle_SOLID;
371 :
372 : // convert line width from EMUs to 1/100mm
373 5435 : sal_Int32 nLineWidth = getLineWidth();
374 :
375 : // create line dash from preset dash token (not for invisible line)
376 5435 : if( (eLineStyle != drawing::LineStyle_NONE) && (moPresetDash.differsFrom( XML_solid ) || !maCustomDash.empty()) )
377 : {
378 61 : LineDash aLineDash;
379 61 : aLineDash.Style = lclGetDashStyle( moLineCap.get( XML_rnd ) );
380 :
381 : // convert preset dash or custom dash
382 61 : if( moPresetDash.differsFrom( XML_solid ) )
383 38 : lclConvertPresetDash( aLineDash, moPresetDash.get() );
384 : else
385 23 : lclConvertCustomDash( aLineDash, maCustomDash );
386 :
387 : // convert relative dash/dot length to absolute length
388 61 : sal_Int32 nBaseLineWidth = ::std::max< sal_Int32 >( nLineWidth, 35 );
389 61 : aLineDash.DotLen *= nBaseLineWidth;
390 61 : aLineDash.DashLen *= nBaseLineWidth;
391 61 : aLineDash.Distance *= nBaseLineWidth;
392 :
393 61 : if( rPropMap.setProperty( SHAPEPROP_LineDash, aLineDash ) )
394 61 : eLineStyle = drawing::LineStyle_DASH;
395 : }
396 :
397 : // set final line style property
398 5435 : rPropMap.setProperty( SHAPEPROP_LineStyle, eLineStyle );
399 :
400 : // line joint type
401 5435 : if( moLineJoint.has() )
402 1567 : rPropMap.setProperty( SHAPEPROP_LineJoint, lclGetLineJoint( moLineJoint.get() ) );
403 :
404 : // line width in 1/100mm
405 5435 : rPropMap.setProperty( SHAPEPROP_LineWidth, nLineWidth );
406 :
407 : // line color and transparence
408 5435 : Color aLineColor = maLineFill.getBestSolidColor();
409 5435 : if( aLineColor.isUsed() )
410 : {
411 2468 : rPropMap.setProperty( SHAPEPROP_LineColor, aLineColor.getColor( rGraphicHelper, nPhClr ) );
412 2468 : if( aLineColor.hasTransparency() )
413 39 : rPropMap.setProperty( SHAPEPROP_LineTransparency, aLineColor.getTransparency() );
414 : }
415 :
416 : // line markers
417 5435 : lclPushMarkerProperties( rPropMap, maStartArrow, nLineWidth, false );
418 5435 : lclPushMarkerProperties( rPropMap, maEndArrow, nLineWidth, true );
419 : }
420 5561 : }
421 :
422 1070 : drawing::LineStyle LineProperties::getLineStyle() const
423 : {
424 : // rules to calculate the line style inferred from the code in LineProperties::pushToPropMap
425 1070 : return (maLineFill.moFillType.get() == XML_noFill) ?
426 : drawing::LineStyle_NONE :
427 1140 : (moPresetDash.differsFrom( XML_solid ) || (!moPresetDash && !maCustomDash.empty())) ?
428 : drawing::LineStyle_DASH :
429 1640 : drawing::LineStyle_SOLID;
430 : }
431 :
432 1070 : drawing::LineJoint LineProperties::getLineJoint() const
433 : {
434 1070 : if( moLineJoint.has() )
435 98 : return lclGetLineJoint( moLineJoint.get() );
436 :
437 972 : return drawing::LineJoint_NONE;
438 : }
439 :
440 6505 : sal_Int32 LineProperties::getLineWidth() const
441 : {
442 6505 : return convertEmuToHmm( moLineWidth.get( 0 ) );
443 : }
444 :
445 : } // namespace drawingml
446 246 : } // namespace oox
447 :
448 : /* vim:set shiftwidth=4 softtabstop=4 expandtab: */
|