LibreOffice Module oox (master) 1
transform2dcontext.cxx
Go to the documentation of this file.
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 <cmath>
21
23
31#include <oox/token/namespaces.hxx>
32
33#include <com/sun/star/awt/Rectangle.hpp>
34
35using namespace ::com::sun::star;
37
38namespace oox::drawingml {
39
41Transform2DContext::Transform2DContext( ContextHandler2Helper const & rParent, const AttributeList& rAttribs, Shape& rShape, bool btxXfrm )
42: ContextHandler2( rParent )
43, mrShape( rShape )
44, mbtxXfrm ( btxXfrm )
45{
46 if( !btxXfrm )
47 {
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 ) );
50 }
51 else
52 {
53 if (rAttribs.hasAttribute(XML_rot) && mrShape.getTextBody())
54 {
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;
58 }
59 }
60}
61
62namespace
63{
64bool ConstructPresetTextRectangle(Shape& rShape, awt::Rectangle& rRect)
65{
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();
70 switch (nType)
71 {
72 case XML_ellipse:
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;
78 return true;
79 case XML_roundRect:
80 case XML_round2SameRect:
81 {
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)
88 return false;
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;
97 return true;
98 }
99 case XML_trapezoid:
100 {
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)
106 return false;
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;
115 return true;
116 }
117 case XML_flowChartManualOperation:
118 {
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;
125 return true;
126 }
127 case XML_pie:
128 case XML_rect:
129 case XML_wedgeRectCallout:
130 {
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;
136 return true;
137 }
138 case XML_gear6:
139 {
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)
144 return false;
145 double a1(15000.0);
146 double a2(3526.0);
147 auto aAdjGdList = rShape.getCustomShapeProperties()->getAdjustmentGuideList();
148 if (aAdjGdList.size() == 2)
149 {
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);
154 }
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;
158
159 double rh = h / 2.0 - th;
160 double rw = w / 2.0 - th;
161
162 double maxr = std::min(rw, rh);
163 double ha = atan2(l3, maxr);
164
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);
175
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
180
181 rRect.X = rShape.getPosition().X + xD5;
182 rRect.Y = rShape.getPosition().Y + yA1;
183 rRect.Width = xA1 - xD5;
184 rRect.Height = yD2 - yA1;
185 return true;
186 }
187 case XML_hexagon:
188 {
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)
194 return false;
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;
204 return true;
205 }
206 case XML_round1Rect:
207 {
208 sal_Int32 nWidth = rShape.getSize().Width;
209 sal_Int32 nHeight = rShape.getSize().Height;
210 if (nWidth == 0 || nHeight == 0)
211 return false;
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;
220 return true;
221 }
222 case XML_rightArrow:
223 {
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)
228 return false;
229 double a1(50000.0);
230 double a2(50000.0);
231 auto aAdjGdList = rShape.getCustomShapeProperties()->getAdjustmentGuideList();
232 if (aAdjGdList.size() == 2)
233 {
234 a1 = aAdjGdList[0].maFormula.toDouble();
235 a2 = aAdjGdList[1].maFormula.toDouble();
236 a1 = std::clamp<double>(a1, 0, 100000);
237 }
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;
249 rRect.Width = x2;
250 rRect.Height = y2 - y1;
251 return true;
252 }
253 default:
254 return false;
255 }
256}
257
258basegfx::B2DPoint getCenter(const awt::Rectangle& rRect)
259{
260 return basegfx::B2DPoint(rRect.X + rRect.Width / 2.0, rRect.Y + rRect.Height / 2.0);
261}
262} // end namespace
263
264ContextHandlerRef Transform2DContext::onCreateContext( sal_Int32 aElementToken, const AttributeList& rAttribs )
265{
266 if (mbtxXfrm)
267 {
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
276
277 switch (aElementToken)
278 {
279 case A_TOKEN(off):
280 {
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())
285 {
286 mno_txXfrmOffX = sXValue.toInt32();
287 mno_txXfrmOffY = sYValue.toInt32();
288 }
289 }
290 break;
291 case A_TOKEN(ext):
292 {
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())
298 {
299 aUnrotatedTxXfrm.Width = sCXValue.toInt32();
300 aUnrotatedTxXfrm.Height = sCYValue.toInt32();
301 }
302 if (mno_txXfrmOffX.has_value() && mno_txXfrmOffY.has_value())
303 {
304 aUnrotatedTxXfrm.X = mno_txXfrmOffX.value();
305 aUnrotatedTxXfrm.Y = mno_txXfrmOffY.value();
306 }
307
308 // Has the txXfrm an own rotation beyond compensation of the shape rotation?
309 // Happens e.g. in diagram type 'Detailed Process'.
310 sal_Int32 nAngleDiff
311 = (mrShape.getRotation() + mno_txXfrmRot.value_or(0)) % 21600000;
312 if (nAngleDiff != 0)
313 {
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));
319
320 if (!aXfrmCenter.equal(aPresetCenter))
321 {
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();
328 }
329 }
330
331 if(mrShape.getTextBody())
332 {
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;
340
341 if (nOffsetLeft)
342 mrShape.getTextBody()->getTextProperties().moTextOffLeft
343 = GetCoordinate(nOffsetLeft);
344 if (nOffsetTop)
345 mrShape.getTextBody()->getTextProperties().moTextOffUpper
346 = GetCoordinate(nOffsetTop);
347 if (nOffsetRight)
348 mrShape.getTextBody()->getTextProperties().moTextOffRight
349 = GetCoordinate(nOffsetRight);
350 if (nOffsetBottom)
351 mrShape.getTextBody()->getTextProperties().moTextOffLower
352 = GetCoordinate(nOffsetBottom);
353 }
354 }
355 break;
356 }
357 return nullptr;
358 } // end of case mbtxXfrm
359
360 switch( aElementToken )
361 {
362 case A_TOKEN( off ): // horz/vert translation
363 mrShape.setPosition( awt::Point( rAttribs.getInteger( XML_x, 0 ), rAttribs.getInteger( XML_y, 0 ) ) );
364 break;
365 case A_TOKEN( ext ): // horz/vert size
366 mrShape.setSize( awt::Size( rAttribs.getInteger( XML_cx, 0 ), rAttribs.getInteger( XML_cy, 0 ) ) );
367 break;
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 ) ) );
370 break;
371 case A_TOKEN( chExt ): // horz/vert size of children
372 {
373 sal_Int32 nChExtCx = rAttribs.getInteger(XML_cx, 0);
374
375 if(nChExtCx == 0)
376 nChExtCx = mrShape.getSize().Width;
377
378 sal_Int32 nChExtCy = rAttribs.getInteger(XML_cy, 0);
379
380 if(nChExtCy == 0)
381 nChExtCy = mrShape.getSize().Height;
382
383 mrShape.setChildSize(awt::Size(nChExtCx, nChExtCy));
384 }
385 break;
386 }
387
388 return nullptr;
389}
390
391} // namespace oox::drawingml
392
393/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
bool equal(const Tuple2D< TYPE > &rTup) const
TYPE getX() const
TYPE getY() const
Provides access to attribute values of an element.
OUString getStringDefaulted(sal_Int32 nAttrToken) const
Returns the string value of the specified attribute, returns an empty string if attribute not present...
bool hasAttribute(sal_Int32 nAttrToken) const
Returns true, if the specified attribute is present.
std::optional< sal_Int32 > getInteger(sal_Int32 nAttrToken) const
Returns the 32-bit signed integer value of the specified attribute (decimal).
std::optional< bool > getBool(sal_Int32 nAttrToken) const
Returns the boolean value of the specified attribute.
CustomShapePropertiesPtr & getCustomShapeProperties()
Definition: shape.hxx:137
void setSize(css::awt::Size aSize)
Definition: shape.hxx:158
const css::awt::Size & getSize() const
Definition: shape.hxx:159
void setRotation(sal_Int32 nRotation)
Definition: shape.hxx:161
const css::awt::Point & getPosition() const
Definition: shape.hxx:156
void setFlip(bool bFlipH, bool bFlipV)
Definition: shape.hxx:164
void setChildPosition(css::awt::Point nPosition)
Definition: shape.hxx:152
sal_Int32 getRotation() const
Definition: shape.hxx:162
void setPosition(css::awt::Point nPosition)
Definition: shape.hxx:155
void setChildSize(css::awt::Size aSize)
Definition: shape.hxx:153
const TextBodyPtr & getTextBody() const
Definition: shape.hxx:195
Transform2DContext(::oox::core::ContextHandler2Helper const &rParent, const ::oox::AttributeList &rAttributes, Shape &rShape, bool btxXfrm=false)
context to import a CT_Transform2D
virtual ::oox::core::ContextHandlerRef onCreateContext(::sal_Int32 Element, const ::oox::AttributeList &rAttribs) override
std::optional< sal_Int32 > mno_txXfrmOffX
std::optional< sal_Int32 > mno_txXfrmOffY
std::optional< sal_Int32 > mno_txXfrmRot
B2DHomMatrix createRotateAroundPoint(double fPointX, double fPointY, double fRadiant)
constexpr double deg2rad(double v)
::rtl::Reference< ContextHandler > ContextHandlerRef
sal_Int32 GetCoordinate(sal_Int32 nValue)
converts EMUs into 1/100th mmm
sal_Int32 h
sal_Int32 w
QPRO_FUNC_TYPE nType