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 
10 #include "WpsContext.hxx"
11 #include <basegfx/matrix/b2dhommatrix.hxx>
12 #include <basegfx/tuple/b2dtuple.hxx>
13 #include <comphelper/sequenceashashmap.hxx>
14 #include <drawingml/customshapeproperties.hxx>
15 #include <com/sun/star/beans/XPropertySet.hpp>
16 #include <com/sun/star/beans/XPropertyState.hpp>
17 #include <com/sun/star/drawing/HomogenMatrix3.hpp>
18 #include <com/sun/star/lang/XServiceInfo.hpp>
19 #include <com/sun/star/text/XText.hpp>
20 #include <com/sun/star/text/XTextCursor.hpp>
21 #include <com/sun/star/text/WritingMode.hpp>
22 #include <svx/svdtrans.hxx>
23 #include <oox/helper/attributelist.hxx>
24 #include <oox/token/namespaces.hxx>
25 #include <oox/token/tokens.hxx>
26 #include <oox/drawingml/shape.hxx>
27 
28 #include <optional>
29 
30 using namespace com::sun::star;
31 
32 namespace oox::shape
33 {
WpsContext(ContextHandler2Helper const & rParent,uno::Reference<drawing::XShape> xShape,const drawingml::ShapePtr & pMasterShapePtr,const drawingml::ShapePtr & pShapePtr)34 WpsContext::WpsContext(ContextHandler2Helper const& rParent, uno::Reference<drawing::XShape> xShape,
35                        const drawingml::ShapePtr& pMasterShapePtr,
36                        const drawingml::ShapePtr& pShapePtr)
37     : ShapeContext(rParent, pMasterShapePtr, pShapePtr)
38     , mxShape(std::move(xShape))
39 {
40     mpShapePtr->setWps(true);
41 }
42 
43 WpsContext::~WpsContext() = default;
44 
onCreateContext(sal_Int32 nElementToken,const oox::AttributeList & rAttribs)45 oox::core::ContextHandlerRef WpsContext::onCreateContext(sal_Int32 nElementToken,
46                                                          const oox::AttributeList& rAttribs)
47 {
48     switch (getBaseToken(nElementToken))
49     {
50         case XML_wsp:
51         case XML_cNvCnPr:
52             break;
53         case XML_bodyPr:
54             if (mxShape.is())
55             {
56                 uno::Reference<lang::XServiceInfo> xServiceInfo(mxShape, uno::UNO_QUERY);
57                 uno::Reference<beans::XPropertySet> xPropertySet(mxShape, uno::UNO_QUERY);
58                 sal_Int32 nVert = rAttribs.getToken(XML_vert, XML_horz);
59                 if (nVert == XML_eaVert)
60                 {
61                     xPropertySet->setPropertyValue("TextWritingMode",
62                                                    uno::makeAny(text::WritingMode_TB_RL));
63                 }
64                 else if (nVert != XML_horz)
65                 {
66                     // Get the existing rotation of the shape.
67                     drawing::HomogenMatrix3 aMatrix;
68                     xPropertySet->getPropertyValue("Transformation") >>= aMatrix;
69                     basegfx::B2DHomMatrix aTransformation;
70                     aTransformation.set(0, 0, aMatrix.Line1.Column1);
71                     aTransformation.set(0, 1, aMatrix.Line1.Column2);
72                     aTransformation.set(0, 2, aMatrix.Line1.Column3);
73                     aTransformation.set(1, 0, aMatrix.Line1.Column1);
74                     aTransformation.set(1, 1, aMatrix.Line2.Column2);
75                     aTransformation.set(1, 2, aMatrix.Line3.Column3);
76                     aTransformation.set(2, 0, aMatrix.Line1.Column1);
77                     aTransformation.set(2, 1, aMatrix.Line2.Column2);
78                     aTransformation.set(2, 2, aMatrix.Line3.Column3);
79                     basegfx::B2DTuple aScale;
80                     basegfx::B2DTuple aTranslate;
81                     double fRotate = 0;
82                     double fShearX = 0;
83                     aTransformation.decompose(aScale, aTranslate, fRotate, fShearX);
84 
85                     // If the text is not rotated the way the shape wants it already, set the angle.
86                     const sal_Int32 nRotation = nVert == XML_vert270 ? -270 : -90;
87                     if (static_cast<sal_Int32>(basegfx::rad2deg(fRotate))
88                         != NormAngle36000(Degree100(nRotation * 100)).get() / 100)
89                     {
90                         comphelper::SequenceAsHashMap aCustomShapeGeometry(
91                             xPropertySet->getPropertyValue("CustomShapeGeometry"));
92                         aCustomShapeGeometry["TextPreRotateAngle"] <<= nRotation;
93                         xPropertySet->setPropertyValue(
94                             "CustomShapeGeometry",
95                             uno::makeAny(aCustomShapeGeometry.getAsConstPropertyValueList()));
96                     }
97                 }
98 
99                 if (bool bUpright = rAttribs.getBool(XML_upright, false))
100                 {
101                     uno::Sequence<beans::PropertyValue> aGrabBag;
102                     xPropertySet->getPropertyValue("InteropGrabBag") >>= aGrabBag;
103                     sal_Int32 length = aGrabBag.getLength();
104                     aGrabBag.realloc(length + 1);
105                     aGrabBag[length].Name = "Upright";
106                     aGrabBag[length].Value <<= bUpright;
107                     xPropertySet->setPropertyValue("InteropGrabBag", uno::makeAny(aGrabBag));
108                 }
109 
110                 if (xServiceInfo.is())
111                 {
112                     // Handle inset attributes for Writer textframes.
113                     sal_Int32 aInsets[] = { XML_lIns, XML_tIns, XML_rIns, XML_bIns };
114                     std::optional<sal_Int32> oInsets[4];
115                     for (std::size_t i = 0; i < SAL_N_ELEMENTS(aInsets); ++i)
116                     {
117                         OptValue<OUString> oValue = rAttribs.getString(aInsets[i]);
118                         if (oValue.has())
119                             oInsets[i] = oox::drawingml::GetCoordinate(oValue.get());
120                         else
121                             // Defaults from the spec: left/right: 91440 EMU, top/bottom: 45720 EMU
122                             oInsets[i]
123                                 = (aInsets[i] == XML_lIns || aInsets[i] == XML_rIns) ? 254 : 127;
124                     }
125                     const OUString aShapeProps[]
126                         = { OUString("TextLeftDistance"), OUString("TextUpperDistance"),
127                             OUString("TextRightDistance"), OUString("TextLowerDistance") };
128                     for (std::size_t i = 0; i < SAL_N_ELEMENTS(aShapeProps); ++i)
129                         if (oInsets[i])
130                             xPropertySet->setPropertyValue(aShapeProps[i],
131                                                            uno::makeAny(*oInsets[i]));
132                 }
133 
134                 // Handle text vertical adjustment inside a text frame
135                 if (rAttribs.hasAttribute(XML_anchor))
136                 {
137                     drawing::TextVerticalAdjust eAdjust
138                         = drawingml::GetTextVerticalAdjust(rAttribs.getToken(XML_anchor, XML_t));
139                     xPropertySet->setPropertyValue("TextVerticalAdjust", uno::makeAny(eAdjust));
140                 }
141 
142                 // Apply character color of the shape to the shape's textbox.
143                 uno::Reference<text::XText> xText(mxShape, uno::UNO_QUERY);
144                 uno::Reference<text::XTextCursor> xTextCursor = xText->createTextCursor();
145                 xTextCursor->gotoStart(false);
146                 xTextCursor->gotoEnd(true);
147                 const uno::Reference<beans::XPropertyState> xPropertyState(xTextCursor,
148                                                                            uno::UNO_QUERY);
149                 const beans::PropertyState ePropertyState
150                     = xPropertyState->getPropertyState("CharColor");
151                 if (ePropertyState == beans::PropertyState_DEFAULT_VALUE)
152                 {
153                     uno::Reference<beans::XPropertySet> xTextBoxPropertySet(xTextCursor,
154                                                                             uno::UNO_QUERY);
155                     uno::Any xCharColor = xPropertySet->getPropertyValue("CharColor");
156                     Color aColor = COL_AUTO;
157                     if (xCharColor >>= aColor)
158                     {
159                         if (aColor != COL_AUTO)
160                             xTextBoxPropertySet->setPropertyValue("CharColor", xCharColor);
161                     }
162                 }
163 
164                 auto nWrappingType = rAttribs.getToken(XML_wrap, XML_square);
165                 xPropertySet->setPropertyValue("TextWordWrap",
166                                                uno::makeAny(nWrappingType == XML_square));
167 
168                 return this;
169             }
170             break;
171         case XML_noAutofit:
172         case XML_spAutoFit:
173         {
174             uno::Reference<lang::XServiceInfo> xServiceInfo(mxShape, uno::UNO_QUERY);
175             // We can't use oox::drawingml::TextBodyPropertiesContext here, as this
176             // is a child context of bodyPr, so the shape is already sent: we need
177             // to alter the XShape directly.
178             uno::Reference<beans::XPropertySet> xPropertySet(mxShape, uno::UNO_QUERY);
179             if (xPropertySet.is())
180             {
181                 if (xServiceInfo->supportsService("com.sun.star.text.TextFrame"))
182                     xPropertySet->setPropertyValue(
183                         "FrameIsAutomaticHeight",
184                         uno::makeAny(getBaseToken(nElementToken) == XML_spAutoFit));
185                 else
186                     xPropertySet->setPropertyValue(
187                         "TextAutoGrowHeight",
188                         uno::makeAny(getBaseToken(nElementToken) == XML_spAutoFit));
189             }
190         }
191         break;
192         case XML_prstTxWarp:
193             if (rAttribs.hasAttribute(XML_prst))
194             {
195                 uno::Reference<beans::XPropertySet> xPropertySet(mxShape, uno::UNO_QUERY);
196                 if (xPropertySet.is())
197                 {
198                     oox::OptValue<OUString> presetShapeName = rAttribs.getString(XML_prst);
199                     const OUString& preset = presetShapeName.get();
200                     comphelper::SequenceAsHashMap aCustomShapeGeometry(
201                         xPropertySet->getPropertyValue("CustomShapeGeometry"));
202                     aCustomShapeGeometry["PresetTextWarp"] <<= preset;
203                     xPropertySet->setPropertyValue(
204                         "CustomShapeGeometry",
205                         uno::makeAny(aCustomShapeGeometry.getAsConstPropertyValueList()));
206                 }
207             }
208             break;
209         case XML_txbx:
210         {
211             mpShapePtr->getCustomShapeProperties()->setShapeTypeOverride(true);
212             mpShapePtr->setTextBox(true);
213             //in case if the textbox is linked, save the attributes
214             //for further processing.
215             if (rAttribs.hasAttribute(XML_id))
216             {
217                 OptValue<OUString> id = rAttribs.getString(XML_id);
218                 if (id.has())
219                 {
220                     oox::drawingml::LinkedTxbxAttr linkedTxtBoxAttr;
221                     linkedTxtBoxAttr.id = id.get().toInt32();
222                     mpShapePtr->setTxbxHasLinkedTxtBox(true);
223                     mpShapePtr->setLinkedTxbxAttributes(linkedTxtBoxAttr);
224                 }
225             }
226             return this;
227         }
228         break;
229         case XML_linkedTxbx:
230         {
231             //in case if the textbox is linked, save the attributes
232             //for further processing.
233             mpShapePtr->getCustomShapeProperties()->setShapeTypeOverride(true);
234             mpShapePtr->setTextBox(true);
235             OptValue<OUString> id = rAttribs.getString(XML_id);
236             OptValue<OUString> seq = rAttribs.getString(XML_seq);
237             if (id.has() && seq.has())
238             {
239                 oox::drawingml::LinkedTxbxAttr linkedTxtBoxAttr;
240                 linkedTxtBoxAttr.id = id.get().toInt32();
241                 linkedTxtBoxAttr.seq = seq.get().toInt32();
242                 mpShapePtr->setTxbxHasLinkedTxtBox(true);
243                 mpShapePtr->setLinkedTxbxAttributes(linkedTxtBoxAttr);
244             }
245         }
246         break;
247         default:
248             return ShapeContext::onCreateContext(nElementToken, rAttribs);
249     }
250     return nullptr;
251 }
252 }
253 
254 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */
255