1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
3 * This file is part of the LibreOffice project.
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/.
9 * This file incorporates work covered by the following license notice:
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 .
22 #include <drawingml/transform2dcontext.hxx>
24 #include <basegfx/matrix/b2dhommatrixtools.hxx>
25 #include <basegfx/numeric/ftools.hxx>
26 #include <basegfx/point/b2dpoint.hxx>
27 #include <drawingml/customshapeproperties.hxx>
28 #include <drawingml/textbody.hxx>
29 #include <oox/drawingml/shape.hxx>
30 #include <oox/helper/attributelist.hxx>
31 #include <oox/token/namespaces.hxx>
33 #include <com/sun/star/awt/Rectangle.hpp>
35 using namespace ::com::sun::star
;
36 using ::oox::core::ContextHandlerRef
;
38 namespace oox::drawingml
{
40 /** context to import a CT_Transform2D */
41 Transform2DContext::Transform2DContext( ContextHandler2Helper
const & rParent
, const AttributeList
& rAttribs
, Shape
& rShape
, bool btxXfrm
)
42 : ContextHandler2( rParent
)
44 , mbtxXfrm ( btxXfrm
)
48 mrShape
.setRotation( rAttribs
.getInteger( XML_rot
, 0 ) ); // 60000ths of a degree Positive angles are clockwise; negative angles are counter-clockwise
49 mrShape
.setFlip( rAttribs
.getBool( XML_flipH
, false ), rAttribs
.getBool( XML_flipV
, false ) );
53 if (rAttribs
.hasAttribute(XML_rot
) && mrShape
.getTextBody())
55 mno_txXfrmRot
= rAttribs
.getInteger(XML_rot
, 0);
56 sal_Int32 nTextAreaRot
= mrShape
.getTextBody()->getTextProperties().moTextAreaRotation
.value_or(0);
57 mrShape
.getTextBody()->getTextProperties().moTextAreaRotation
= mno_txXfrmRot
.value() + nTextAreaRot
;
64 bool ConstructPresetTextRectangle(Shape
& rShape
, awt::Rectangle
& rRect
)
66 // When we are here, we have neither xShape nor a SdrObject. So need to manually calc the text
67 // area rectangle defined in the preset in OOXML standard, but only for those types of shapes
68 // where we know, that MS Office SmartArt presets do not use the default text area rectangle.
69 const sal_Int32 nType
= rShape
.getCustomShapeProperties()->getShapePresetType();
73 // The preset text rectangle touches the perimeter of the ellipse at 45deg.
74 rRect
.X
= rShape
.getPosition().X
+ rShape
.getSize().Width
* ((1.0 - M_SQRT1_2
) / 2.0);
75 rRect
.Y
= rShape
.getPosition().Y
+ rShape
.getSize().Height
* ((1.0 - M_SQRT1_2
) / 2.0);
76 rRect
.Width
= rShape
.getSize().Width
* M_SQRT1_2
;
77 rRect
.Height
= rShape
.getSize().Height
* M_SQRT1_2
;
80 case XML_round2SameRect
:
82 // Second handle of round2SameRect used in preset diagrams has value 0.
83 auto aAdjGdList
= rShape
.getCustomShapeProperties()->getAdjustmentGuideList();
84 double fAdj
= aAdjGdList
.empty() ? 16667 : aAdjGdList
[0].maFormula
.toDouble();
85 sal_Int32 nWidth
= rShape
.getSize().Width
;
86 sal_Int32 nHeight
= rShape
.getSize().Height
;
87 if (nWidth
== 0 || nHeight
== 0)
89 double fMaxAdj
= 50000.0 * nWidth
/ std::min(nWidth
, nHeight
);
90 fAdj
= std::clamp
<double>(fAdj
, 0, fMaxAdj
);
91 sal_Int32 nTextLeft
= std::min(nWidth
, nHeight
) * fAdj
/ 100000.0 * 0.29289;
92 sal_Int32 nTextTop
= nTextLeft
;
93 rRect
.X
= rShape
.getPosition().X
+ nTextLeft
;
94 rRect
.Y
= rShape
.getPosition().Y
+ nTextTop
;
95 rRect
.Width
= nWidth
- 2 * nTextLeft
;
96 rRect
.Height
= nHeight
- (nType
== XML_roundRect
? 2 : 1) * nTextTop
;
101 auto aAdjGdList
= rShape
.getCustomShapeProperties()->getAdjustmentGuideList();
102 double fAdj
= aAdjGdList
.empty() ? 25000 : aAdjGdList
[0].maFormula
.toDouble();
103 sal_Int32 nWidth
= rShape
.getSize().Width
;
104 sal_Int32 nHeight
= rShape
.getSize().Height
;
105 if (nWidth
== 0 || nHeight
== 0)
107 double fMaxAdj
= 50000.0 * nWidth
/ std::min(nWidth
, nHeight
);
108 fAdj
= std::clamp
<double>(fAdj
, 0, fMaxAdj
);
109 sal_Int32 nTextLeft
= nWidth
/ 3.0 * fAdj
/ fMaxAdj
;
110 sal_Int32 nTextTop
= nHeight
/ 3.0 * fAdj
/ fMaxAdj
;
111 rRect
.X
= rShape
.getPosition().X
+ nTextLeft
;
112 rRect
.Y
= rShape
.getPosition().Y
+ nTextTop
;
113 rRect
.Width
= nWidth
- 2 * nTextLeft
;
114 rRect
.Height
= nHeight
- 2 * nTextTop
;
117 case XML_flowChartManualOperation
:
119 sal_Int32 nWidth
= rShape
.getSize().Width
;
120 sal_Int32 nTextLeft
= nWidth
/ 5;
121 rRect
.X
= rShape
.getPosition().X
+ nTextLeft
;
122 rRect
.Y
= rShape
.getPosition().Y
;
123 rRect
.Width
= nWidth
- 2 * nTextLeft
;
124 rRect
.Height
= rShape
.getSize().Height
;
129 case XML_wedgeRectCallout
:
131 // When tdf#149918 is fixed, pie will need its own case
132 rRect
.X
= rShape
.getPosition().X
;
133 rRect
.Y
= rShape
.getPosition().Y
;
134 rRect
.Width
= rShape
.getSize().Width
;
135 rRect
.Height
= rShape
.getSize().Height
;
140 // The identifiers here reflect the guides name value in presetShapeDefinitions.xml
141 double w
= rShape
.getSize().Width
;
142 double h
= rShape
.getSize().Height
;
143 if (w
<= 0 || h
<= 0)
147 auto aAdjGdList
= rShape
.getCustomShapeProperties()->getAdjustmentGuideList();
148 if (aAdjGdList
.size() == 2)
150 a1
= aAdjGdList
[0].maFormula
.toDouble();
151 a2
= aAdjGdList
[1].maFormula
.toDouble();
152 a1
= std::clamp
<double>(a1
, 0, 20000);
153 a2
= std::clamp
<double>(a2
, 0, 5358);
155 double th
= std::min(w
, h
) * a1
/ 100000.0;
156 double l2
= std::min(w
, h
) * a2
/ 100000.0 / 2.0;
157 double l3
= th
/ 2.0 + l2
;
159 double rh
= h
/ 2.0 - th
;
160 double rw
= w
/ 2.0 - th
;
162 double maxr
= std::min(rw
, rh
);
163 double ha
= atan2(l3
, maxr
);
165 double aA1
= basegfx::deg2rad(330) - ha
;
166 double ta11
= rw
* cos(aA1
);
167 double ta12
= rh
* sin(aA1
);
168 double bA1
= atan2(ta12
, ta11
);
169 double cta1
= rh
* cos(bA1
);
170 double sta1
= rw
* sin(bA1
);
171 double ma1
= std::hypot(cta1
, sta1
);
172 double na1
= rw
* rh
/ ma1
;
173 double dxa1
= na1
* cos(bA1
);
174 double dya1
= na1
* sin(bA1
);
176 double xA1
= w
/ 2.0 + dxa1
; // r
177 double yA1
= h
/ 2.0 + dya1
; // t
178 double yD2
= h
- yA1
; // b
179 double xD5
= w
- xA1
; // l
181 rRect
.X
= rShape
.getPosition().X
+ xD5
;
182 rRect
.Y
= rShape
.getPosition().Y
+ yA1
;
183 rRect
.Width
= xA1
- xD5
;
184 rRect
.Height
= yD2
- yA1
;
189 auto aAdjGdList
= rShape
.getCustomShapeProperties()->getAdjustmentGuideList();
190 double fAdj
= aAdjGdList
.empty() ? 25000 : aAdjGdList
[0].maFormula
.toDouble();
191 sal_Int32 nWidth
= rShape
.getSize().Width
;
192 sal_Int32 nHeight
= rShape
.getSize().Height
;
193 if (nWidth
== 0 || nHeight
== 0)
195 double fMaxAdj
= 50000.0 * nWidth
/ std::min(nWidth
, nHeight
);
196 fAdj
= std::clamp
<double>(fAdj
, 0, fMaxAdj
);
197 double fFactor
= fAdj
/ fMaxAdj
/ 6.0 + 1.0 / 12.0;
198 sal_Int32 nTextLeft
= nWidth
* fFactor
;
199 sal_Int32 nTextTop
= nHeight
* fFactor
;
200 rRect
.X
= rShape
.getPosition().X
+ nTextLeft
;
201 rRect
.Y
= rShape
.getPosition().Y
+ nTextTop
;
202 rRect
.Width
= nWidth
- 2 * nTextLeft
;
203 rRect
.Height
= nHeight
- 2 * nTextTop
;
208 sal_Int32 nWidth
= rShape
.getSize().Width
;
209 sal_Int32 nHeight
= rShape
.getSize().Height
;
210 if (nWidth
== 0 || nHeight
== 0)
212 auto aAdjGdList
= rShape
.getCustomShapeProperties()->getAdjustmentGuideList();
213 double fAdj
= aAdjGdList
.empty() ? 16667.0 : aAdjGdList
[0].maFormula
.toDouble();
214 fAdj
= std::clamp
<double>(fAdj
, 0.0, 50000.0);
215 double fDx
= std::min(nWidth
, nHeight
) * fAdj
/ 100000.0 * 0.29289;
216 rRect
.X
= rShape
.getPosition().X
;
217 rRect
.Y
= rShape
.getPosition().Y
;
218 rRect
.Width
= nWidth
- fDx
;
219 rRect
.Height
= nHeight
;
224 // The identifiers here reflect the guides name value in presetShapeDefinitions.xml
225 sal_Int32 nWidth
= rShape
.getSize().Width
;
226 sal_Int32 nHeight
= rShape
.getSize().Height
;
227 if (nWidth
== 0 || nHeight
== 0)
231 auto aAdjGdList
= rShape
.getCustomShapeProperties()->getAdjustmentGuideList();
232 if (aAdjGdList
.size() == 2)
234 a1
= aAdjGdList
[0].maFormula
.toDouble();
235 a2
= aAdjGdList
[1].maFormula
.toDouble();
236 a1
= std::clamp
<double>(a1
, 0, 100000);
238 double maxAdj2
= 100000.0 * nWidth
/ std::min(nWidth
, nHeight
);
239 a2
= std::clamp
<double>(a2
, 0, maxAdj2
);
240 double dx1
= std::min(nWidth
, nHeight
) * a2
/ 100000.0;
241 double x1
= nWidth
- dx1
;
242 double dy1
= nHeight
* a1
/ 200000.0;
243 double y1
= nHeight
/ 2.0 - dy1
; // top
244 double y2
= nHeight
/ 2.0 + dy1
; // bottom
245 double dx2
= y1
* dx1
/ (nHeight
/ 2.0);
246 double x2
= x1
+ dx2
; // right
247 rRect
.X
= rShape
.getPosition().X
; // left = 0
248 rRect
.Y
= rShape
.getPosition().Y
+ y1
;
250 rRect
.Height
= y2
- y1
;
258 basegfx::B2DPoint
getCenter(const awt::Rectangle
& rRect
)
260 return basegfx::B2DPoint(rRect
.X
+ rRect
.Width
/ 2.0, rRect
.Y
+ rRect
.Height
/ 2.0);
264 ContextHandlerRef
Transform2DContext::onCreateContext( sal_Int32 aElementToken
, const AttributeList
& rAttribs
)
268 // The child elements <a:off> and <a:ext> of a <dsp:txXfrm> element describe the position and
269 // size of the text area rectangle. We cannot change the text area rectangle directly, because
270 // currently we depend on the geometry definition of the preset. As workaround we change the
271 // indents to move and scale the text block. The needed shifts are calculated here as moTextOff
272 // and used in TextBodyProperties::pushTextDistances().
273 awt::Rectangle aPresetTextRectangle
;
274 if (!ConstructPresetTextRectangle(mrShape
, aPresetTextRectangle
))
275 return nullptr; // faulty shape or text area calculation not implemented
277 switch (aElementToken
)
281 // need <a:ext> too, so only save values here.
282 const OUString sXValue
= rAttribs
.getStringDefaulted(XML_x
);
283 const OUString sYValue
= rAttribs
.getStringDefaulted(XML_y
);
284 if (!sXValue
.isEmpty() && !sYValue
.isEmpty())
286 mno_txXfrmOffX
= sXValue
.toInt32();
287 mno_txXfrmOffY
= sYValue
.toInt32();
293 // Build text frame from txXfrm element
294 awt::Rectangle aUnrotatedTxXfrm
= aPresetTextRectangle
; // dummy initialize
295 const OUString sCXValue
= rAttribs
.getStringDefaulted(XML_cx
);
296 const OUString sCYValue
= rAttribs
.getStringDefaulted(XML_cy
);
297 if (!sCXValue
.isEmpty() && !sCYValue
.isEmpty())
299 aUnrotatedTxXfrm
.Width
= sCXValue
.toInt32();
300 aUnrotatedTxXfrm
.Height
= sCYValue
.toInt32();
302 if (mno_txXfrmOffX
.has_value() && mno_txXfrmOffY
.has_value())
304 aUnrotatedTxXfrm
.X
= mno_txXfrmOffX
.value();
305 aUnrotatedTxXfrm
.Y
= mno_txXfrmOffY
.value();
308 // Has the txXfrm an own rotation beyond compensation of the shape rotation?
309 // Happens e.g. in diagram type 'Detailed Process'.
311 = (mrShape
.getRotation() + mno_txXfrmRot
.value_or(0)) % 21600000;
314 // Rectangle aUnrotatedTxXfrm rotates around its center not around text area
315 // center from preset. We shift aUnrotatedTxXfrm so that it is at the original
316 // position after rotation of text area rectangle from preset.
317 basegfx::B2DPoint
aXfrmCenter(getCenter(aUnrotatedTxXfrm
));
318 basegfx::B2DPoint
aPresetCenter(getCenter(aPresetTextRectangle
));
320 if (!aXfrmCenter
.equal(aPresetCenter
))
322 double fAngleRad
= basegfx::deg2rad(nAngleDiff
/ 60000.0);
323 basegfx::B2DHomMatrix
aRotMatrix(
324 basegfx::utils::createRotateAroundPoint(aPresetCenter
, -fAngleRad
));
325 basegfx::B2DPoint
aNewCenter(aRotMatrix
* aXfrmCenter
);
326 aUnrotatedTxXfrm
.X
+= aNewCenter
.getX() - aXfrmCenter
.getX();
327 aUnrotatedTxXfrm
.Y
+= aNewCenter
.getY() - aXfrmCenter
.getY();
331 if(mrShape
.getTextBody())
333 // Calculate indent offsets
334 sal_Int32 nOffsetLeft
= aUnrotatedTxXfrm
.X
- aPresetTextRectangle
.X
;
335 sal_Int32 nOffsetTop
= aUnrotatedTxXfrm
.Y
- aPresetTextRectangle
.Y
;
336 sal_Int32 nOffsetRight
337 = aPresetTextRectangle
.Width
- aUnrotatedTxXfrm
.Width
- nOffsetLeft
;
338 sal_Int32 nOffsetBottom
339 = aPresetTextRectangle
.Height
- aUnrotatedTxXfrm
.Height
- nOffsetTop
;
342 mrShape
.getTextBody()->getTextProperties().moTextOffLeft
343 = GetCoordinate(nOffsetLeft
);
345 mrShape
.getTextBody()->getTextProperties().moTextOffUpper
346 = GetCoordinate(nOffsetTop
);
348 mrShape
.getTextBody()->getTextProperties().moTextOffRight
349 = GetCoordinate(nOffsetRight
);
351 mrShape
.getTextBody()->getTextProperties().moTextOffLower
352 = GetCoordinate(nOffsetBottom
);
358 } // end of case mbtxXfrm
360 switch( aElementToken
)
362 case A_TOKEN( off
): // horz/vert translation
363 mrShape
.setPosition( awt::Point( rAttribs
.getInteger( XML_x
, 0 ), rAttribs
.getInteger( XML_y
, 0 ) ) );
365 case A_TOKEN( ext
): // horz/vert size
366 mrShape
.setSize( awt::Size( rAttribs
.getInteger( XML_cx
, 0 ), rAttribs
.getInteger( XML_cy
, 0 ) ) );
368 case A_TOKEN( chOff
): // horz/vert translation of children
369 mrShape
.setChildPosition( awt::Point( rAttribs
.getInteger( XML_x
, 0 ), rAttribs
.getInteger( XML_y
, 0 ) ) );
371 case A_TOKEN( chExt
): // horz/vert size of children
373 sal_Int32 nChExtCx
= rAttribs
.getInteger(XML_cx
, 0);
376 nChExtCx
= mrShape
.getSize().Width
;
378 sal_Int32 nChExtCy
= rAttribs
.getInteger(XML_cy
, 0);
381 nChExtCy
= mrShape
.getSize().Height
;
383 mrShape
.setChildSize(awt::Size(nChExtCx
, nChExtCy
));
391 } // namespace oox::drawingml
393 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */