/src/libreoffice/oox/source/export/drawingml.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 <config_features.h> |
21 | | |
22 | | #include <config_folders.h> |
23 | | #include <rtl/bootstrap.hxx> |
24 | | #include <sal/log.hxx> |
25 | | #include <oox/core/xmlfilterbase.hxx> |
26 | | #include <oox/export/drawingml.hxx> |
27 | | #include <oox/export/utils.hxx> |
28 | | #include <oox/helper/propertyset.hxx> |
29 | | #include <oox/drawingml/color.hxx> |
30 | | #include <drawingml/fillproperties.hxx> |
31 | | #include <drawingml/fontworkhelpers.hxx> |
32 | | #include <drawingml/textparagraph.hxx> |
33 | | #include <oox/token/namespaces.hxx> |
34 | | #include <oox/token/properties.hxx> |
35 | | #include <oox/token/relationship.hxx> |
36 | | #include <oox/token/tokens.hxx> |
37 | | #include <oox/drawingml/drawingmltypes.hxx> |
38 | | #include <svtools/unitconv.hxx> |
39 | | #include <sax/fastattribs.hxx> |
40 | | #include <comphelper/diagnose_ex.hxx> |
41 | | #include <comphelper/processfactory.hxx> |
42 | | #include <i18nlangtag/languagetag.hxx> |
43 | | #include <basegfx/matrix/b2dhommatrixtools.hxx> |
44 | | #include <basegfx/range/b2drange.hxx> |
45 | | #include <basegfx/utils/gradienttools.hxx> |
46 | | |
47 | | #include <numeric> |
48 | | #include <string_view> |
49 | | |
50 | | #include <com/sun/star/awt/CharSet.hpp> |
51 | | #include <com/sun/star/awt/FontDescriptor.hpp> |
52 | | #include <com/sun/star/awt/FontSlant.hpp> |
53 | | #include <com/sun/star/awt/FontStrikeout.hpp> |
54 | | #include <com/sun/star/awt/FontWeight.hpp> |
55 | | #include <com/sun/star/awt/FontUnderline.hpp> |
56 | | #include <com/sun/star/awt/Gradient.hpp> |
57 | | #include <com/sun/star/awt/Gradient2.hpp> |
58 | | #include <com/sun/star/beans/XPropertySet.hpp> |
59 | | #include <com/sun/star/beans/XPropertyState.hpp> |
60 | | #include <com/sun/star/beans/XPropertySetInfo.hpp> |
61 | | #include <com/sun/star/container/XEnumerationAccess.hpp> |
62 | | #include <com/sun/star/container/XIndexAccess.hpp> |
63 | | #include <com/sun/star/container/XNameAccess.hpp> |
64 | | #include <com/sun/star/drawing/BitmapMode.hpp> |
65 | | #include <com/sun/star/drawing/ColorMode.hpp> |
66 | | #include <com/sun/star/drawing/EnhancedCustomShapeAdjustmentValue.hpp> |
67 | | #include <com/sun/star/drawing/EnhancedCustomShapeParameterPair.hpp> |
68 | | #include <com/sun/star/drawing/EnhancedCustomShapeParameterType.hpp> |
69 | | #include <com/sun/star/drawing/EnhancedCustomShapeSegment.hpp> |
70 | | #include <com/sun/star/drawing/EnhancedCustomShapeSegmentCommand.hpp> |
71 | | #include <com/sun/star/drawing/FillStyle.hpp> |
72 | | #include <com/sun/star/drawing/Hatch.hpp> |
73 | | #include <com/sun/star/drawing/LineDash.hpp> |
74 | | #include <com/sun/star/drawing/LineJoint.hpp> |
75 | | #include <com/sun/star/drawing/LineStyle.hpp> |
76 | | #include <com/sun/star/drawing/TextFitToSizeType.hpp> |
77 | | #include <com/sun/star/drawing/TextHorizontalAdjust.hpp> |
78 | | #include <com/sun/star/drawing/TextVerticalAdjust.hpp> |
79 | | #include <com/sun/star/drawing/XShape.hpp> |
80 | | #include <com/sun/star/drawing/XShapes.hpp> |
81 | | #include <com/sun/star/frame/XModel.hpp> |
82 | | #include <com/sun/star/graphic/XGraphic.hpp> |
83 | | #include <com/sun/star/i18n/ScriptType.hpp> |
84 | | #include <com/sun/star/i18n/BreakIterator.hpp> |
85 | | #include <com/sun/star/i18n/XBreakIterator.hpp> |
86 | | #include <com/sun/star/io/XOutputStream.hpp> |
87 | | #include <com/sun/star/lang/XMultiServiceFactory.hpp> |
88 | | #include <com/sun/star/style/LineSpacing.hpp> |
89 | | #include <com/sun/star/style/LineSpacingMode.hpp> |
90 | | #include <com/sun/star/text/WritingMode.hpp> |
91 | | #include <com/sun/star/text/WritingMode2.hpp> |
92 | | #include <com/sun/star/text/GraphicCrop.hpp> |
93 | | #include <com/sun/star/text/XText.hpp> |
94 | | #include <com/sun/star/text/XTextColumns.hpp> |
95 | | #include <com/sun/star/text/XTextContent.hpp> |
96 | | #include <com/sun/star/text/XTextField.hpp> |
97 | | #include <com/sun/star/text/XTextRange.hpp> |
98 | | #include <com/sun/star/text/XTextFrame.hpp> |
99 | | #include <com/sun/star/style/CaseMap.hpp> |
100 | | #include <com/sun/star/xml/dom/XNodeList.hpp> |
101 | | #include <com/sun/star/xml/sax/Writer.hpp> |
102 | | #include <com/sun/star/xml/sax/XSAXSerializable.hpp> |
103 | | #include <com/sun/star/container/XNamed.hpp> |
104 | | #include <com/sun/star/drawing/XDrawPages.hpp> |
105 | | #include <com/sun/star/drawing/XDrawPagesSupplier.hpp> |
106 | | #include <com/sun/star/drawing/RectanglePoint.hpp> |
107 | | |
108 | | #include <comphelper/propertyvalue.hxx> |
109 | | #include <comphelper/random.hxx> |
110 | | #include <comphelper/seqstream.hxx> |
111 | | #include <comphelper/storagehelper.hxx> |
112 | | #include <comphelper/xmltools.hxx> |
113 | | #include <o3tl/any.hxx> |
114 | | #include <o3tl/safeint.hxx> |
115 | | #include <o3tl/string_view.hxx> |
116 | | #include <tools/stream.hxx> |
117 | | #include <tools/UnitConversion.hxx> |
118 | | #include <unotools/fontdefs.hxx> |
119 | | #include <vcl/cvtgrf.hxx> |
120 | | #include <vcl/svapp.hxx> |
121 | | #include <vcl/embeddedfontshelper.hxx> |
122 | | #include <rtl/strbuf.hxx> |
123 | | #include <filter/msfilter/escherex.hxx> |
124 | | #include <filter/msfilter/util.hxx> |
125 | | #include <editeng/outlobj.hxx> |
126 | | #include <editeng/svxenum.hxx> |
127 | | #include <editeng/unonames.hxx> |
128 | | #include <editeng/unoprnms.hxx> |
129 | | #include <editeng/flditem.hxx> |
130 | | #include <editeng/escapementitem.hxx> |
131 | | #include <editeng/unonrule.hxx> |
132 | | #include <docmodel/uno/UnoComplexColor.hxx> |
133 | | #include <svx/svdoashp.hxx> |
134 | | #include <svx/svdomedia.hxx> |
135 | | #include <svx/svdtrans.hxx> |
136 | | #include <svx/unoshape.hxx> |
137 | | #include <svx/EnhancedCustomShape2d.hxx> |
138 | | #include <drawingml/presetgeometrynames.hxx> |
139 | | #include <docmodel/uno/UnoGradientTools.hxx> |
140 | | |
141 | | using namespace ::css; |
142 | | using namespace ::css::beans; |
143 | | using namespace ::css::drawing; |
144 | | using namespace ::css::i18n; |
145 | | using namespace ::css::style; |
146 | | using namespace ::css::text; |
147 | | using namespace ::css::uno; |
148 | | using namespace ::css::container; |
149 | | using namespace ::com::sun::star::drawing::EnhancedCustomShapeSegmentCommand; |
150 | | |
151 | | using ::css::io::XOutputStream; |
152 | | using ::sax_fastparser::FSHelperPtr; |
153 | | using ::sax_fastparser::FastSerializerHelper; |
154 | | |
155 | | namespace |
156 | | { |
157 | | const char* g_aPredefinedClrNames[] = { |
158 | | "dk1", |
159 | | "lt1", |
160 | | "dk2", |
161 | | "lt2", |
162 | | "accent1", |
163 | | "accent2", |
164 | | "accent3", |
165 | | "accent4", |
166 | | "accent5", |
167 | | "accent6", |
168 | | "hlink", |
169 | | "folHlink", |
170 | | }; |
171 | | |
172 | | /** converts 1/100mm to the ST_TextSpacingPoint (1/100pt) */ |
173 | | sal_Int64 toTextSpacingPoint(sal_Int64 mm100) |
174 | 0 | { |
175 | 0 | constexpr auto mdToPt = o3tl::getConversionMulDiv(o3tl::Length::mm100, o3tl::Length::pt); |
176 | 0 | constexpr o3tl::detail::m_and_d md(mdToPt.first * 100, mdToPt.second); |
177 | 0 | return o3tl::convert(mm100, md.m, md.d); |
178 | 0 | } |
179 | | } |
180 | | |
181 | | namespace oox::drawingml { |
182 | | |
183 | | URLTransformer::~URLTransformer() |
184 | 0 | { |
185 | 0 | } |
186 | | |
187 | | OUString URLTransformer::getTransformedString(const OUString& rString) const |
188 | 0 | { |
189 | 0 | return rString; |
190 | 0 | } |
191 | | |
192 | | bool URLTransformer::isExternalURL(const OUString& rURL) const |
193 | 0 | { |
194 | 0 | bool bExternal = true; |
195 | 0 | if (rURL.startsWith("#")) |
196 | 0 | bExternal = false; |
197 | 0 | return bExternal; |
198 | 0 | } |
199 | | |
200 | | GraphicExportCache& GraphicExportCache::get() |
201 | 0 | { |
202 | 0 | static GraphicExportCache staticGraphicExportCache; |
203 | 0 | return staticGraphicExportCache; |
204 | 0 | } |
205 | | |
206 | | static css::uno::Any getLineDash( const css::uno::Reference<css::frame::XModel>& xModel, const OUString& rDashName ) |
207 | 0 | { |
208 | 0 | css::uno::Reference<css::lang::XMultiServiceFactory> xFact(xModel, css::uno::UNO_QUERY); |
209 | 0 | css::uno::Reference<css::container::XNameAccess> xNameAccess( |
210 | 0 | xFact->createInstance(u"com.sun.star.drawing.DashTable"_ustr), |
211 | 0 | css::uno::UNO_QUERY ); |
212 | 0 | if(xNameAccess.is()) |
213 | 0 | { |
214 | 0 | if (!xNameAccess->hasByName(rDashName)) |
215 | 0 | return css::uno::Any(); |
216 | | |
217 | 0 | return xNameAccess->getByName(rDashName); |
218 | 0 | } |
219 | | |
220 | 0 | return css::uno::Any(); |
221 | 0 | } |
222 | | |
223 | | namespace |
224 | | { |
225 | | void WriteGradientPath(const basegfx::BGradient& rBGradient, const FSHelperPtr& pFS, const bool bCircle) |
226 | 0 | { |
227 | 0 | pFS->startElementNS(XML_a, XML_path, XML_path, bCircle ? "circle" : "rect"); |
228 | | |
229 | | // Write the focus rectangle. Work with the focus point, and assume |
230 | | // that it extends 50% in all directions. The below |
231 | | // left/top/right/bottom values are percentages, where 0 means the |
232 | | // edge of the tile rectangle and 100% means the center of it. |
233 | 0 | rtl::Reference<sax_fastparser::FastAttributeList> pAttributeList( |
234 | 0 | sax_fastparser::FastSerializerHelper::createAttrList()); |
235 | 0 | sal_Int32 nLeftPercent = rBGradient.GetXOffset(); |
236 | 0 | pAttributeList->add(XML_l, OString::number(nLeftPercent * PER_PERCENT)); |
237 | 0 | sal_Int32 nTopPercent = rBGradient.GetYOffset(); |
238 | 0 | pAttributeList->add(XML_t, OString::number(nTopPercent * PER_PERCENT)); |
239 | 0 | sal_Int32 nRightPercent = 100 - rBGradient.GetXOffset(); |
240 | 0 | pAttributeList->add(XML_r, OString::number(nRightPercent * PER_PERCENT)); |
241 | 0 | sal_Int32 nBottomPercent = 100 - rBGradient.GetYOffset(); |
242 | 0 | pAttributeList->add(XML_b, OString::number(nBottomPercent * PER_PERCENT)); |
243 | 0 | pFS->singleElementNS(XML_a, XML_fillToRect, pAttributeList); |
244 | |
|
245 | 0 | pFS->endElementNS(XML_a, XML_path); |
246 | 0 | } |
247 | | } |
248 | | |
249 | | // not thread safe |
250 | | sal_Int32 DrawingML::mnDrawingMLCount = 0; |
251 | | sal_Int32 DrawingML::mnVmlCount = 0; |
252 | | sal_Int32 DrawingML::mnChartCount = 0; |
253 | | |
254 | | DrawingML::DrawingML(::sax_fastparser::FSHelperPtr pFS, ::oox::core::XmlFilterBase* pFB, DocumentType eDocumentType, DMLTextExport* pTextExport) |
255 | 0 | : meDocumentType(eDocumentType) |
256 | 0 | , mpTextExport(pTextExport) |
257 | 0 | , mpFS(std::move(pFS)) |
258 | 0 | , mpFB(pFB) |
259 | 0 | , mbIsBackgroundDark(false) |
260 | 0 | , mbPlaceholder(false) |
261 | 0 | { |
262 | 0 | uno::Reference<beans::XPropertySet> xSettings(pFB->getModelFactory()->createInstance(u"com.sun.star.document.Settings"_ustr), uno::UNO_QUERY); |
263 | 0 | if (xSettings.is()) |
264 | 0 | { |
265 | 0 | try |
266 | 0 | { |
267 | 0 | xSettings->getPropertyValue(u"EmbedFonts"_ustr) >>= mbEmbedFonts; |
268 | 0 | } |
269 | 0 | catch (Exception& ) |
270 | 0 | { |
271 | 0 | } |
272 | 0 | } |
273 | 0 | } |
274 | | |
275 | | sal_Int16 DrawingML::GetScriptType(const OUString& rStr) |
276 | 0 | { |
277 | 0 | if (rStr.getLength() > 0) |
278 | 0 | { |
279 | 0 | static Reference<css::i18n::XBreakIterator> xBreakIterator = |
280 | 0 | css::i18n::BreakIterator::create(comphelper::getProcessComponentContext()); |
281 | |
|
282 | 0 | sal_Int16 nScriptType = xBreakIterator->getScriptType(rStr, 0); |
283 | |
|
284 | 0 | if (nScriptType == css::i18n::ScriptType::WEAK) |
285 | 0 | { |
286 | 0 | sal_Int32 nPos = xBreakIterator->nextScript(rStr, 0, nScriptType); |
287 | 0 | if (nPos < rStr.getLength()) |
288 | 0 | nScriptType = xBreakIterator->getScriptType(rStr, nPos); |
289 | |
|
290 | 0 | } |
291 | |
|
292 | 0 | if (nScriptType != css::i18n::ScriptType::WEAK) |
293 | 0 | return nScriptType; |
294 | 0 | } |
295 | | |
296 | 0 | return css::i18n::ScriptType::LATIN; |
297 | 0 | } |
298 | | |
299 | | void DrawingML::ResetMlCounters() |
300 | 0 | { |
301 | 0 | mnDrawingMLCount = 0; |
302 | 0 | mnVmlCount = 0; |
303 | 0 | mnChartCount = 0; |
304 | 0 | } |
305 | | |
306 | | bool DrawingML::GetProperty( const Reference< XPropertySet >& rXPropertySet, const OUString& aName ) |
307 | 0 | { |
308 | 0 | try |
309 | 0 | { |
310 | 0 | mAny = rXPropertySet->getPropertyValue(aName); |
311 | 0 | if (mAny.hasValue()) |
312 | 0 | return true; |
313 | 0 | } |
314 | 0 | catch( const Exception& ) |
315 | 0 | { |
316 | | /* printf ("exception when trying to get value of property: %s\n", aName.toUtf8()); */ |
317 | 0 | } |
318 | 0 | return false; |
319 | 0 | } |
320 | | |
321 | | bool DrawingML::GetPropertyAndState( const Reference< XPropertySet >& rXPropertySet, const Reference< XPropertyState >& rXPropertyState, const OUString& aName, PropertyState& eState ) |
322 | 0 | { |
323 | 0 | try |
324 | 0 | { |
325 | 0 | mAny = rXPropertySet->getPropertyValue(aName); |
326 | 0 | if (mAny.hasValue()) |
327 | 0 | { |
328 | 0 | eState = rXPropertyState->getPropertyState(aName); |
329 | 0 | return true; |
330 | 0 | } |
331 | 0 | } |
332 | 0 | catch( const Exception& ) |
333 | 0 | { |
334 | | /* printf ("exception when trying to get value of property: %s\n", aName.toUtf8()); */ |
335 | 0 | } |
336 | 0 | return false; |
337 | 0 | } |
338 | | |
339 | | namespace |
340 | | { |
341 | | /// Gets hexa value of color on string format. |
342 | | OString getColorStr(const ::Color nColor) |
343 | 0 | { |
344 | | // Transparency is a separate element. |
345 | 0 | OString sColor = OString::number(sal_uInt32(nColor) & 0x00FFFFFF, 16); |
346 | 0 | if (sColor.getLength() < 6) |
347 | 0 | { |
348 | 0 | OStringBuffer sBuf("0"); |
349 | 0 | int remains = 5 - sColor.getLength(); |
350 | |
|
351 | 0 | while (remains > 0) |
352 | 0 | { |
353 | 0 | sBuf.append("0"); |
354 | 0 | remains--; |
355 | 0 | } |
356 | |
|
357 | 0 | sBuf.append(sColor); |
358 | |
|
359 | 0 | sColor = sBuf.toString(); |
360 | 0 | } |
361 | 0 | return sColor; |
362 | 0 | } |
363 | | } |
364 | | |
365 | | void DrawingML::WriteColor( ::Color nColor, sal_Int32 nAlpha ) |
366 | 0 | { |
367 | 0 | const auto sColor = getColorStr(nColor); |
368 | 0 | if( nAlpha < MAX_PERCENT ) |
369 | 0 | { |
370 | 0 | mpFS->startElementNS(XML_a, XML_srgbClr, XML_val, sColor); |
371 | 0 | mpFS->singleElementNS(XML_a, XML_alpha, XML_val, OString::number(nAlpha)); |
372 | 0 | mpFS->endElementNS( XML_a, XML_srgbClr ); |
373 | |
|
374 | 0 | } |
375 | 0 | else |
376 | 0 | { |
377 | 0 | mpFS->singleElementNS(XML_a, XML_srgbClr, XML_val, sColor); |
378 | 0 | } |
379 | 0 | } |
380 | | |
381 | | void DrawingML::WriteColor( const OUString& sColorSchemeName, const Sequence< PropertyValue >& aTransformations, sal_Int32 nAlpha ) |
382 | 0 | { |
383 | | // prevent writing a tag with empty val attribute |
384 | 0 | if( sColorSchemeName.isEmpty() ) |
385 | 0 | return; |
386 | | |
387 | 0 | if( aTransformations.hasElements() ) |
388 | 0 | { |
389 | 0 | mpFS->startElementNS(XML_a, XML_schemeClr, XML_val, sColorSchemeName); |
390 | 0 | WriteColorTransformations( aTransformations, nAlpha ); |
391 | 0 | mpFS->endElementNS( XML_a, XML_schemeClr ); |
392 | 0 | } |
393 | 0 | else if(nAlpha < MAX_PERCENT) |
394 | 0 | { |
395 | 0 | mpFS->startElementNS(XML_a, XML_schemeClr, XML_val, sColorSchemeName); |
396 | 0 | mpFS->singleElementNS(XML_a, XML_alpha, XML_val, OString::number(nAlpha)); |
397 | 0 | mpFS->endElementNS( XML_a, XML_schemeClr ); |
398 | 0 | } |
399 | 0 | else |
400 | 0 | { |
401 | 0 | mpFS->singleElementNS(XML_a, XML_schemeClr, XML_val, sColorSchemeName); |
402 | 0 | } |
403 | 0 | } |
404 | | |
405 | | void DrawingML::WriteColor( const ::Color nColor, const Sequence< PropertyValue >& aTransformations, sal_Int32 nAlpha ) |
406 | 0 | { |
407 | 0 | const auto sColor = getColorStr(nColor); |
408 | 0 | if( aTransformations.hasElements() ) |
409 | 0 | { |
410 | 0 | mpFS->startElementNS(XML_a, XML_srgbClr, XML_val, sColor); |
411 | 0 | WriteColorTransformations(aTransformations, nAlpha); |
412 | 0 | mpFS->endElementNS(XML_a, XML_srgbClr); |
413 | 0 | } |
414 | 0 | else if(nAlpha < MAX_PERCENT) |
415 | 0 | { |
416 | 0 | mpFS->startElementNS(XML_a, XML_srgbClr, XML_val, sColor); |
417 | 0 | mpFS->singleElementNS(XML_a, XML_alpha, XML_val, OString::number(nAlpha)); |
418 | 0 | mpFS->endElementNS(XML_a, XML_srgbClr); |
419 | 0 | } |
420 | 0 | else |
421 | 0 | { |
422 | 0 | mpFS->singleElementNS(XML_a, XML_srgbClr, XML_val, sColor); |
423 | 0 | } |
424 | 0 | } |
425 | | |
426 | | void DrawingML::WriteColorTransformations( const Sequence< PropertyValue >& aTransformations, sal_Int32 nAlpha ) |
427 | 0 | { |
428 | 0 | for( const auto& rTransformation : aTransformations ) |
429 | 0 | { |
430 | 0 | sal_Int32 nToken = Color::getColorTransformationToken( rTransformation.Name ); |
431 | 0 | if( nToken != XML_TOKEN_INVALID && rTransformation.Value.hasValue() ) |
432 | 0 | { |
433 | 0 | if(nToken == XML_alpha && nAlpha < MAX_PERCENT) |
434 | 0 | { |
435 | 0 | mpFS->singleElementNS(XML_a, nToken, XML_val, OString::number(nAlpha)); |
436 | 0 | } |
437 | 0 | else |
438 | 0 | { |
439 | 0 | sal_Int32 nValue = rTransformation.Value.get<sal_Int32>(); |
440 | 0 | mpFS->singleElementNS(XML_a, nToken, XML_val, OString::number(nValue)); |
441 | 0 | } |
442 | 0 | } |
443 | 0 | } |
444 | 0 | } |
445 | | |
446 | | void DrawingML::WriteSolidFill( ::Color nColor, sal_Int32 nAlpha ) |
447 | 0 | { |
448 | 0 | mpFS->startElementNS(XML_a, XML_solidFill); |
449 | 0 | WriteColor( nColor, nAlpha ); |
450 | 0 | mpFS->endElementNS( XML_a, XML_solidFill ); |
451 | 0 | } |
452 | | |
453 | | void DrawingML::WriteSolidFill( const OUString& sSchemeName, const Sequence< PropertyValue >& aTransformations, sal_Int32 nAlpha ) |
454 | 0 | { |
455 | 0 | mpFS->startElementNS(XML_a, XML_solidFill); |
456 | 0 | WriteColor( sSchemeName, aTransformations, nAlpha ); |
457 | 0 | mpFS->endElementNS( XML_a, XML_solidFill ); |
458 | 0 | } |
459 | | |
460 | | void DrawingML::WriteSolidFill( const ::Color nColor, const Sequence< PropertyValue >& aTransformations, sal_Int32 nAlpha ) |
461 | 0 | { |
462 | 0 | mpFS->startElementNS(XML_a, XML_solidFill); |
463 | 0 | WriteColor(nColor, aTransformations, nAlpha); |
464 | 0 | mpFS->endElementNS(XML_a, XML_solidFill); |
465 | 0 | } |
466 | | |
467 | | void DrawingML::WriteSolidFill( const Reference< XPropertySet >& rXPropSet ) |
468 | 0 | { |
469 | | // get fill color |
470 | 0 | if ( !GetProperty( rXPropSet, u"FillColor"_ustr ) ) |
471 | 0 | return; |
472 | 0 | sal_uInt32 nFillColor = mAny.get<sal_uInt32>(); |
473 | | |
474 | | // get InteropGrabBag and search the relevant attributes |
475 | 0 | OUString sColorFillScheme; |
476 | 0 | sal_uInt32 nOriginalColor = 0; |
477 | 0 | Sequence< PropertyValue > aStyleProperties, aTransformations; |
478 | 0 | if ( GetProperty( rXPropSet, u"InteropGrabBag"_ustr ) ) |
479 | 0 | { |
480 | 0 | Sequence< PropertyValue > aGrabBag; |
481 | 0 | mAny >>= aGrabBag; |
482 | 0 | for (const auto& rProp : aGrabBag) |
483 | 0 | { |
484 | 0 | if( rProp.Name == "SpPrSolidFillSchemeClr" ) |
485 | 0 | rProp.Value >>= sColorFillScheme; |
486 | 0 | else if( rProp.Name == "OriginalSolidFillClr" ) |
487 | 0 | rProp.Value >>= nOriginalColor; |
488 | 0 | else if( rProp.Name == "StyleFillRef" ) |
489 | 0 | rProp.Value >>= aStyleProperties; |
490 | 0 | else if( rProp.Name == "SpPrSolidFillSchemeClrTransformations" ) |
491 | 0 | rProp.Value >>= aTransformations; |
492 | 0 | } |
493 | 0 | } |
494 | |
|
495 | 0 | sal_Int32 nAlpha = MAX_PERCENT; |
496 | 0 | if( GetProperty( rXPropSet, u"FillTransparence"_ustr ) ) |
497 | 0 | { |
498 | 0 | sal_Int32 nTransparency = 0; |
499 | 0 | mAny >>= nTransparency; |
500 | | // Calculate alpha value (see oox/source/drawingml/color.cxx : getTransparency()) |
501 | 0 | nAlpha = (MAX_PERCENT - ( PER_PERCENT * nTransparency ) ); |
502 | 0 | } |
503 | | |
504 | | // OOXML has no separate transparence gradient but uses transparency in the gradient stops. |
505 | | // So we merge transparency and color and use gradient fill in such case. |
506 | 0 | basegfx::BGradient aTransparenceGradient; |
507 | 0 | OUString sFillTransparenceGradientName; |
508 | 0 | bool bNeedGradientFill(false); |
509 | |
|
510 | 0 | if (GetProperty(rXPropSet, u"FillTransparenceGradientName"_ustr) |
511 | 0 | && (mAny >>= sFillTransparenceGradientName) |
512 | 0 | && !sFillTransparenceGradientName.isEmpty() |
513 | 0 | && GetProperty(rXPropSet, u"FillTransparenceGradient"_ustr)) |
514 | 0 | { |
515 | 0 | aTransparenceGradient = model::gradient::getFromAny(mAny); |
516 | 0 | basegfx::BColor aSingleColor; |
517 | 0 | bNeedGradientFill = !aTransparenceGradient.GetColorStops().isSingleColor(aSingleColor); |
518 | | |
519 | | // we no longer need to 'guess' if FillTransparenceGradient is used by |
520 | | // comparing it's 1st color to COL_BLACK after having tested that the |
521 | | // FillTransparenceGradientName is set |
522 | 0 | if (!bNeedGradientFill) |
523 | 0 | { |
524 | | // Our alpha is a gray color value. |
525 | 0 | const sal_uInt8 nRed(aSingleColor.getRed() * 255.0); |
526 | | |
527 | | // drawingML alpha is a percentage on a 0..100000 scale. |
528 | 0 | nAlpha = (255 - nRed) * oox::drawingml::MAX_PERCENT / 255; |
529 | 0 | } |
530 | 0 | } |
531 | | |
532 | | // write XML |
533 | 0 | if (bNeedGradientFill) |
534 | 0 | { |
535 | | // no longer create copy/PseudoColorGradient, use new API of |
536 | | // WriteGradientFill to express fix fill color |
537 | 0 | mpFS->startElementNS(XML_a, XML_gradFill, XML_rotWithShape, "0"); |
538 | 0 | WriteGradientFill(nullptr, nFillColor, &aTransparenceGradient); |
539 | 0 | mpFS->endElementNS( XML_a, XML_gradFill ); |
540 | 0 | } |
541 | 0 | else if ( nFillColor != nOriginalColor ) |
542 | 0 | { |
543 | | // the user has set a different color for the shape |
544 | 0 | if (!WriteSchemeColor(u"FillComplexColor"_ustr, rXPropSet)) |
545 | 0 | { |
546 | 0 | WriteSolidFill(::Color(ColorTransparency, nFillColor & 0xffffff), nAlpha); |
547 | 0 | } |
548 | 0 | } |
549 | | // tdf#91332 LO doesn't export the actual theme.xml in XLSX. |
550 | 0 | else if ( !sColorFillScheme.isEmpty() && GetDocumentType() != DOCUMENT_XLSX ) |
551 | 0 | { |
552 | | // the shape had a scheme color and the user didn't change it |
553 | 0 | WriteSolidFill( sColorFillScheme, aTransformations, nAlpha ); |
554 | 0 | } |
555 | 0 | else |
556 | 0 | { |
557 | | // the shape had a custom color and the user didn't change it |
558 | | // tdf#124013 |
559 | 0 | WriteSolidFill( ::Color(ColorTransparency, nFillColor & 0xffffff), nAlpha ); |
560 | 0 | } |
561 | 0 | } |
562 | | |
563 | | bool DrawingML::WriteSchemeColor(OUString const& rPropertyName, const uno::Reference<beans::XPropertySet>& xPropertySet) |
564 | 0 | { |
565 | 0 | if (!xPropertySet->getPropertySetInfo()->hasPropertyByName(rPropertyName)) |
566 | 0 | return false; |
567 | | |
568 | 0 | uno::Reference<util::XComplexColor> xComplexColor; |
569 | 0 | xPropertySet->getPropertyValue(rPropertyName) >>= xComplexColor; |
570 | 0 | if (!xComplexColor.is()) |
571 | 0 | return false; |
572 | | |
573 | 0 | auto aComplexColor = model::color::getFromXComplexColor(xComplexColor); |
574 | 0 | if (aComplexColor.getThemeColorType() == model::ThemeColorType::Unknown) |
575 | 0 | return false; |
576 | 0 | const char* pColorName = g_aPredefinedClrNames[sal_Int16(aComplexColor.getThemeColorType())]; |
577 | 0 | mpFS->startElementNS(XML_a, XML_solidFill); |
578 | 0 | mpFS->startElementNS(XML_a, XML_schemeClr, XML_val, pColorName); |
579 | 0 | for (auto const& rTransform : aComplexColor.getTransformations()) |
580 | 0 | { |
581 | 0 | switch (rTransform.meType) |
582 | 0 | { |
583 | 0 | case model::TransformationType::LumMod: |
584 | 0 | mpFS->singleElementNS(XML_a, XML_lumMod, XML_val, OString::number(rTransform.mnValue * 10)); |
585 | 0 | break; |
586 | 0 | case model::TransformationType::LumOff: |
587 | 0 | mpFS->singleElementNS(XML_a, XML_lumOff, XML_val, OString::number(rTransform.mnValue * 10)); |
588 | 0 | break; |
589 | 0 | case model::TransformationType::Tint: |
590 | 0 | mpFS->singleElementNS(XML_a, XML_tint, XML_val, OString::number(rTransform.mnValue * 10)); |
591 | 0 | break; |
592 | 0 | case model::TransformationType::Shade: |
593 | 0 | mpFS->singleElementNS(XML_a, XML_shade, XML_val, OString::number(rTransform.mnValue * 10)); |
594 | 0 | break; |
595 | 0 | default: |
596 | 0 | break; |
597 | 0 | } |
598 | 0 | } |
599 | | // Alpha is actually not contained in maTransformations although possible (as of Mar 2023). |
600 | 0 | sal_Int16 nAPITransparency(0); |
601 | 0 | if ((rPropertyName == u"FillComplexColor" && GetProperty(xPropertySet, u"FillTransparence"_ustr)) |
602 | 0 | || (rPropertyName == u"LineComplexColor" && GetProperty(xPropertySet, u"LineTransparence"_ustr)) |
603 | 0 | || (rPropertyName == u"CharComplexColor" && GetProperty(xPropertySet, u"CharTransparence"_ustr))) |
604 | 0 | { |
605 | 0 | mAny >>= nAPITransparency; |
606 | 0 | } |
607 | 0 | if (nAPITransparency != 0) |
608 | 0 | mpFS->singleElementNS(XML_a, XML_alpha, XML_val, |
609 | 0 | OString::number(MAX_PERCENT - (PER_PERCENT * nAPITransparency))); |
610 | |
|
611 | 0 | mpFS->endElementNS(XML_a, XML_schemeClr); |
612 | 0 | mpFS->endElementNS(XML_a, XML_solidFill); |
613 | |
|
614 | 0 | return true; |
615 | 0 | } |
616 | | |
617 | | void DrawingML::WriteGradientStop(double fOffset, const basegfx::BColor& rColor, const basegfx::BColor& rAlpha) |
618 | 0 | { |
619 | 0 | mpFS->startElementNS(XML_a, XML_gs, XML_pos, OString::number(basegfx::fround64(fOffset * 100000))); |
620 | 0 | WriteColor( |
621 | 0 | ::Color(rColor), |
622 | 0 | basegfx::fround((1.0 - rAlpha.luminance()) * oox::drawingml::MAX_PERCENT)); |
623 | 0 | mpFS->endElementNS( XML_a, XML_gs ); |
624 | 0 | } |
625 | | |
626 | | ::Color DrawingML::ColorWithIntensity( sal_uInt32 nColor, sal_uInt32 nIntensity ) |
627 | 0 | { |
628 | 0 | return ::Color(ColorTransparency, ( ( ( nColor & 0xff ) * nIntensity ) / 100 ) |
629 | 0 | | ( ( ( ( ( nColor & 0xff00 ) >> 8 ) * nIntensity ) / 100 ) << 8 ) |
630 | 0 | | ( ( ( ( ( nColor & 0xff0000 ) >> 8 ) * nIntensity ) / 100 ) << 8 )); |
631 | 0 | } |
632 | | |
633 | | void DrawingML::WriteGradientFill( const Reference< XPropertySet >& rXPropSet ) |
634 | 0 | { |
635 | 0 | if (!GetProperty(rXPropSet, u"FillGradient"_ustr)) |
636 | 0 | return; |
637 | | |
638 | | // use BGradient constructor directly, it will take care of Gradient/Gradient2 |
639 | 0 | basegfx::BGradient aGradient = model::gradient::getFromAny(mAny); |
640 | | |
641 | | // get InteropGrabBag and search the relevant attributes |
642 | 0 | basegfx::BGradient aOriginalGradient; |
643 | 0 | Sequence< PropertyValue > aGradientStops; |
644 | 0 | if ( GetProperty( rXPropSet, u"InteropGrabBag"_ustr ) ) |
645 | 0 | { |
646 | 0 | Sequence< PropertyValue > aGrabBag; |
647 | 0 | mAny >>= aGrabBag; |
648 | 0 | for (const auto& rProp : aGrabBag) |
649 | 0 | if( rProp.Name == "GradFillDefinition" ) |
650 | 0 | rProp.Value >>= aGradientStops; |
651 | 0 | else if( rProp.Name == "OriginalGradFill" ) |
652 | 0 | aOriginalGradient = model::gradient::getFromAny(rProp.Value); |
653 | 0 | } |
654 | | |
655 | | // check if an ooxml gradient had been imported and if the user has modified it |
656 | | // Gradient grab-bag depends on theme grab-bag, which is implemented |
657 | | // only for DOCX. |
658 | 0 | if (aOriginalGradient == aGradient && GetDocumentType() == DOCUMENT_DOCX) |
659 | 0 | { |
660 | | // If we have no gradient stops that means original gradient were defined by a theme. |
661 | 0 | if( aGradientStops.hasElements() ) |
662 | 0 | { |
663 | 0 | mpFS->startElementNS(XML_a, XML_gradFill, XML_rotWithShape, "0"); |
664 | 0 | WriteGrabBagGradientFill(aGradientStops, aGradient); |
665 | 0 | mpFS->endElementNS( XML_a, XML_gradFill ); |
666 | 0 | } |
667 | 0 | } |
668 | 0 | else |
669 | 0 | { |
670 | 0 | mpFS->startElementNS(XML_a, XML_gradFill, XML_rotWithShape, "0"); |
671 | |
|
672 | 0 | basegfx::BGradient aTransparenceGradient; |
673 | 0 | basegfx::BGradient* pTransparenceGradient(nullptr); |
674 | 0 | double fTransparency(0.0); |
675 | 0 | OUString sFillTransparenceGradientName; |
676 | |
|
677 | 0 | if (GetProperty(rXPropSet, u"FillTransparenceGradientName"_ustr) |
678 | 0 | && (mAny >>= sFillTransparenceGradientName) |
679 | 0 | && !sFillTransparenceGradientName.isEmpty() |
680 | 0 | && GetProperty(rXPropSet, u"FillTransparenceGradient"_ustr)) |
681 | 0 | { |
682 | | // TransparenceGradient is only used when name is not empty |
683 | 0 | aTransparenceGradient = model::gradient::getFromAny(mAny); |
684 | 0 | pTransparenceGradient = &aTransparenceGradient; |
685 | 0 | } |
686 | 0 | else if (GetProperty(rXPropSet, u"FillTransparence"_ustr)) |
687 | 0 | { |
688 | | // no longer create PseudoTransparencyGradient, use new API of |
689 | | // WriteGradientFill to express fix transparency |
690 | 0 | sal_Int32 nTransparency(0); |
691 | 0 | mAny >>= nTransparency; |
692 | | // nTransparency is [0..100]% |
693 | 0 | fTransparency = nTransparency * 0.01; |
694 | 0 | } |
695 | | |
696 | | // tdf#155852 The gradient might wrongly have StepCount==0, as the draw:gradient-step-count |
697 | | // attribute in ODF does not belong to the gradient definition but is an attribute in |
698 | | // the graphic style of the shape. |
699 | 0 | if (GetProperty(rXPropSet, u"FillGradientStepCount"_ustr)) |
700 | 0 | { |
701 | 0 | sal_Int16 nStepCount = 0; |
702 | 0 | mAny >>= nStepCount; |
703 | 0 | aGradient.SetSteps(nStepCount); |
704 | 0 | } |
705 | |
|
706 | 0 | WriteGradientFill(&aGradient, 0, pTransparenceGradient, fTransparency); |
707 | |
|
708 | 0 | mpFS->endElementNS(XML_a, XML_gradFill); |
709 | 0 | } |
710 | 0 | } |
711 | | |
712 | | void DrawingML::WriteGrabBagGradientFill( const Sequence< PropertyValue >& aGradientStops, const basegfx::BGradient& rBGradient ) |
713 | 0 | { |
714 | | // write back the original gradient |
715 | 0 | mpFS->startElementNS(XML_a, XML_gsLst); |
716 | | |
717 | | // get original stops and write them |
718 | 0 | for( const auto& rGradientStop : aGradientStops ) |
719 | 0 | { |
720 | 0 | Sequence< PropertyValue > aGradientStop; |
721 | 0 | rGradientStop.Value >>= aGradientStop; |
722 | | |
723 | | // get values |
724 | 0 | OUString sSchemeClr; |
725 | 0 | double nPos = 0; |
726 | 0 | sal_Int16 nTransparency = 0; |
727 | 0 | ::Color nRgbClr; |
728 | 0 | Sequence< PropertyValue > aTransformations; |
729 | 0 | for (const auto& rProp : aGradientStop) |
730 | 0 | { |
731 | 0 | if( rProp.Name == "SchemeClr" ) |
732 | 0 | rProp.Value >>= sSchemeClr; |
733 | 0 | else if( rProp.Name == "RgbClr" ) |
734 | 0 | rProp.Value >>= nRgbClr; |
735 | 0 | else if( rProp.Name == "Pos" ) |
736 | 0 | rProp.Value >>= nPos; |
737 | 0 | else if( rProp.Name == "Transparency" ) |
738 | 0 | rProp.Value >>= nTransparency; |
739 | 0 | else if( rProp.Name == "Transformations" ) |
740 | 0 | rProp.Value >>= aTransformations; |
741 | 0 | } |
742 | | // write stop |
743 | 0 | mpFS->startElementNS(XML_a, XML_gs, XML_pos, OString::number(nPos * 100000.0)); |
744 | 0 | if( sSchemeClr.isEmpty() ) |
745 | 0 | { |
746 | | // Calculate alpha value (see oox/source/drawingml/color.cxx : getTransparency()) |
747 | 0 | sal_Int32 nAlpha = MAX_PERCENT - ( PER_PERCENT * nTransparency ); |
748 | 0 | WriteColor( nRgbClr, nAlpha ); |
749 | 0 | } |
750 | 0 | else |
751 | 0 | { |
752 | 0 | WriteColor( sSchemeClr, aTransformations ); |
753 | 0 | } |
754 | 0 | mpFS->endElementNS( XML_a, XML_gs ); |
755 | 0 | } |
756 | 0 | mpFS->endElementNS( XML_a, XML_gsLst ); |
757 | |
|
758 | 0 | switch (rBGradient.GetGradientStyle()) |
759 | 0 | { |
760 | 0 | default: |
761 | 0 | { |
762 | 0 | const sal_Int16 nAngle(rBGradient.GetAngle()); |
763 | 0 | mpFS->singleElementNS( |
764 | 0 | XML_a, XML_lin, XML_ang, |
765 | 0 | OString::number(((3600 - static_cast<sal_Int32>(nAngle) + 900) * 6000) % 21600000)); |
766 | 0 | break; |
767 | 0 | } |
768 | 0 | case awt::GradientStyle_RADIAL: |
769 | 0 | { |
770 | 0 | WriteGradientPath(rBGradient, mpFS, true); |
771 | 0 | break; |
772 | 0 | } |
773 | 0 | } |
774 | 0 | } |
775 | | |
776 | | void DrawingML::WriteGradientFill( |
777 | | const basegfx::BGradient* pColorGradient, sal_Int32 nFixColor, |
778 | | const basegfx::BGradient* pTransparenceGradient, double fFixTransparence) |
779 | 0 | { |
780 | 0 | basegfx::BColorStops aColorStops; |
781 | 0 | basegfx::BColorStops aAlphaStops; |
782 | 0 | basegfx::BColor aSingleColor(::Color(ColorTransparency, nFixColor).getBColor()); |
783 | 0 | basegfx::BColor aSingleAlpha(fFixTransparence); |
784 | 0 | const basegfx::BGradient* pGradient(pColorGradient); |
785 | |
|
786 | 0 | if (nullptr != pColorGradient) |
787 | 0 | { |
788 | | // extract and correct/process ColorStops |
789 | 0 | basegfx::utils::prepareColorStops(*pColorGradient, aColorStops, aSingleColor); |
790 | | |
791 | | // tdf#155827 Convert 'axial' to 'linear' before synchronize and for each gradient separate. |
792 | 0 | if (aColorStops.size() > 0 && awt::GradientStyle_AXIAL == pColorGradient->GetGradientStyle()) |
793 | 0 | aColorStops.doApplyAxial(); |
794 | 0 | } |
795 | 0 | if (nullptr != pTransparenceGradient) |
796 | 0 | { |
797 | | // remember basic Gradient definition to use |
798 | | // So we can get the gradient geometry in any case from pGradient. |
799 | 0 | if (nullptr == pGradient) |
800 | 0 | { |
801 | 0 | pGradient = pTransparenceGradient; |
802 | 0 | } |
803 | | |
804 | | // extract and correct/process AlphaStops |
805 | 0 | basegfx::utils::prepareColorStops(*pTransparenceGradient, aAlphaStops, aSingleAlpha); |
806 | 0 | if (aAlphaStops.size() > 0 |
807 | 0 | && awt::GradientStyle_AXIAL == pTransparenceGradient->GetGradientStyle()) |
808 | 0 | { |
809 | 0 | aAlphaStops.doApplyAxial(); |
810 | 0 | } |
811 | 0 | } |
812 | |
|
813 | 0 | if (nullptr == pGradient) |
814 | 0 | { |
815 | | // an error - see comment in header - is to give neither pColorGradient |
816 | | // nor pTransparenceGradient |
817 | 0 | assert(false && "pColorGradient or pTransparenceGradient should be set"); |
818 | 0 | return; |
819 | 0 | } |
820 | | |
821 | | // Apply steps if used. That increases the number of stops and thus needs to be done before |
822 | | // synchronize. |
823 | 0 | if (pGradient->GetSteps()) |
824 | 0 | { |
825 | 0 | aColorStops.doApplySteps(pGradient->GetSteps()); |
826 | | // transparency gradients are always automatic, so do not have steps. |
827 | 0 | } |
828 | | |
829 | | // synchronize ColorStops and AlphaStops as preparation to export |
830 | | // so also gradients 'coupled' indirectly using the 'FillTransparenceGradient' |
831 | | // method (at import time) will be exported again. |
832 | 0 | basegfx::utils::synchronizeColorStops(aColorStops, aAlphaStops, aSingleColor, aSingleAlpha); |
833 | |
|
834 | 0 | if (aColorStops.size() != aAlphaStops.size()) |
835 | 0 | { |
836 | | // this is an error - synchronizeColorStops above *has* to create that |
837 | | // state, see description there (!) |
838 | 0 | assert(false && "oox::WriteGradientFill: non-synchronized gradients (!)"); |
839 | 0 | return; |
840 | 0 | } |
841 | | |
842 | 0 | bool bLinearOrAxial(awt::GradientStyle_LINEAR == pGradient->GetGradientStyle() |
843 | 0 | || awt::GradientStyle_AXIAL == pGradient->GetGradientStyle()); |
844 | 0 | if (!bLinearOrAxial) |
845 | 0 | { |
846 | | // case awt::GradientStyle_RADIAL: |
847 | | // case awt::GradientStyle_ELLIPTICAL: |
848 | | // case awt::GradientStyle_RECT: |
849 | | // case awt::GradientStyle_SQUARE: |
850 | | // all these types need the gradients to be mirrored |
851 | 0 | aColorStops.reverseColorStops(); |
852 | 0 | aAlphaStops.reverseColorStops(); |
853 | 0 | } |
854 | | |
855 | | // If there were one stop, prepareColorStops() method would have cleared aColorStops, same for |
856 | | // aAlphaStops. In case of empty stops vectors synchronizeColorStops() method creates two stops |
857 | | // for each. So at this point we have at least two stops and can fulfill the requirement of |
858 | | // <gsLst> element to have at least two child elements. |
859 | | |
860 | | // export GradientStops (with alpha) |
861 | 0 | mpFS->startElementNS(XML_a, XML_gsLst); |
862 | |
|
863 | 0 | basegfx::BColorStops::const_iterator aCurrColor(aColorStops.begin()); |
864 | 0 | basegfx::BColorStops::const_iterator aCurrAlpha(aAlphaStops.begin()); |
865 | |
|
866 | 0 | while (aCurrColor != aColorStops.end() && aCurrAlpha != aAlphaStops.end()) |
867 | 0 | { |
868 | 0 | WriteGradientStop( |
869 | 0 | aCurrColor->getStopOffset(), |
870 | 0 | aCurrColor->getStopColor(), |
871 | 0 | aCurrAlpha->getStopColor()); |
872 | 0 | aCurrColor++; |
873 | 0 | aCurrAlpha++; |
874 | 0 | } |
875 | |
|
876 | 0 | mpFS->endElementNS( XML_a, XML_gsLst ); |
877 | |
|
878 | 0 | if (bLinearOrAxial) |
879 | 0 | { |
880 | | // CT_LinearShadeProperties, cases where gradient rotation has to be exported |
881 | | // 'scaled' does not exist in LO, so only 'ang'. |
882 | 0 | const sal_Int16 nAngle(pGradient->GetAngle()); |
883 | 0 | mpFS->singleElementNS( |
884 | 0 | XML_a, XML_lin, XML_ang, |
885 | 0 | OString::number(((3600 - static_cast<sal_Int32>(nAngle) + 900) * 6000) % 21600000)); |
886 | 0 | } |
887 | 0 | else |
888 | 0 | { |
889 | | // CT_PathShadeProperties, cases where gradient path has to be exported |
890 | | // Concentric fill is not yet implemented, therefore no type 'shape', only 'circle' or 'rect' |
891 | 0 | const bool bCircle(pGradient->GetGradientStyle() == awt::GradientStyle_RADIAL || |
892 | 0 | pGradient->GetGradientStyle() == awt::GradientStyle_ELLIPTICAL); |
893 | 0 | WriteGradientPath(*pGradient, mpFS, bCircle); |
894 | 0 | } |
895 | 0 | } |
896 | | |
897 | | void DrawingML::WriteLineArrow( const Reference< XPropertySet >& rXPropSet, bool bLineStart ) |
898 | 0 | { |
899 | 0 | ESCHER_LineEnd eLineEnd; |
900 | 0 | sal_Int32 nArrowLength; |
901 | 0 | sal_Int32 nArrowWidth; |
902 | |
|
903 | 0 | if ( !EscherPropertyContainer::GetLineArrow( bLineStart, rXPropSet, eLineEnd, nArrowLength, nArrowWidth ) ) |
904 | 0 | return; |
905 | | |
906 | 0 | const char* len; |
907 | 0 | const char* type; |
908 | 0 | const char* width; |
909 | |
|
910 | 0 | switch( nArrowLength ) |
911 | 0 | { |
912 | 0 | case ESCHER_LineShortArrow: |
913 | 0 | len = "sm"; |
914 | 0 | break; |
915 | 0 | default: |
916 | 0 | case ESCHER_LineMediumLenArrow: |
917 | 0 | len = "med"; |
918 | 0 | break; |
919 | 0 | case ESCHER_LineLongArrow: |
920 | 0 | len = "lg"; |
921 | 0 | break; |
922 | 0 | } |
923 | | |
924 | 0 | switch( eLineEnd ) |
925 | 0 | { |
926 | 0 | default: |
927 | 0 | case ESCHER_LineNoEnd: |
928 | 0 | type = "none"; |
929 | 0 | break; |
930 | 0 | case ESCHER_LineArrowEnd: |
931 | 0 | type = "triangle"; |
932 | 0 | break; |
933 | 0 | case ESCHER_LineArrowStealthEnd: |
934 | 0 | type = "stealth"; |
935 | 0 | break; |
936 | 0 | case ESCHER_LineArrowDiamondEnd: |
937 | 0 | type = "diamond"; |
938 | 0 | break; |
939 | 0 | case ESCHER_LineArrowOvalEnd: |
940 | 0 | type = "oval"; |
941 | 0 | break; |
942 | 0 | case ESCHER_LineArrowOpenEnd: |
943 | 0 | type = "arrow"; |
944 | 0 | break; |
945 | 0 | } |
946 | | |
947 | 0 | switch( nArrowWidth ) |
948 | 0 | { |
949 | 0 | case ESCHER_LineNarrowArrow: |
950 | 0 | width = "sm"; |
951 | 0 | break; |
952 | 0 | default: |
953 | 0 | case ESCHER_LineMediumWidthArrow: |
954 | 0 | width = "med"; |
955 | 0 | break; |
956 | 0 | case ESCHER_LineWideArrow: |
957 | 0 | width = "lg"; |
958 | 0 | break; |
959 | 0 | } |
960 | | |
961 | 0 | mpFS->singleElementNS( XML_a, bLineStart ? XML_headEnd : XML_tailEnd, |
962 | 0 | XML_len, len, |
963 | 0 | XML_type, type, |
964 | 0 | XML_w, width ); |
965 | 0 | } |
966 | | |
967 | | void DrawingML::WriteOutline( const Reference<XPropertySet>& rXPropSet, Reference< frame::XModel > const & xModel ) |
968 | 0 | { |
969 | 0 | drawing::LineStyle aLineStyle( drawing::LineStyle_NONE ); |
970 | 0 | if (GetProperty(rXPropSet, u"LineStyle"_ustr)) |
971 | 0 | mAny >>= aLineStyle; |
972 | |
|
973 | 0 | const LineCap aLineCap = GetProperty(rXPropSet, u"LineCap"_ustr) ? mAny.get<drawing::LineCap>() : LineCap_BUTT; |
974 | |
|
975 | 0 | sal_uInt32 nLineWidth = 0; |
976 | 0 | sal_uInt32 nEmuLineWidth = 0; |
977 | 0 | ::Color nColor; |
978 | 0 | sal_Int32 nColorAlpha = MAX_PERCENT; |
979 | 0 | bool bColorSet = false; |
980 | 0 | const char* cap = nullptr; |
981 | 0 | drawing::LineDash aLineDash; |
982 | 0 | bool bDashSet = false; |
983 | 0 | bool bNoFill = false; |
984 | | |
985 | | |
986 | | // get InteropGrabBag and search the relevant attributes |
987 | 0 | OUString sColorFillScheme; |
988 | 0 | ::Color aResolvedColorFillScheme; |
989 | |
|
990 | 0 | ::Color nOriginalColor; |
991 | 0 | ::Color nStyleColor; |
992 | 0 | sal_uInt32 nStyleLineWidth = 0; |
993 | |
|
994 | 0 | Sequence<PropertyValue> aStyleProperties; |
995 | 0 | Sequence<PropertyValue> aTransformations; |
996 | |
|
997 | 0 | drawing::LineStyle aStyleLineStyle(drawing::LineStyle_NONE); |
998 | 0 | drawing::LineJoint aStyleLineJoint(drawing::LineJoint_NONE); |
999 | |
|
1000 | 0 | if (GetProperty(rXPropSet, u"InteropGrabBag"_ustr)) |
1001 | 0 | { |
1002 | 0 | Sequence<PropertyValue> aGrabBag; |
1003 | 0 | mAny >>= aGrabBag; |
1004 | |
|
1005 | 0 | for (const auto& rProp : aGrabBag) |
1006 | 0 | { |
1007 | 0 | if( rProp.Name == "SpPrLnSolidFillSchemeClr" ) |
1008 | 0 | rProp.Value >>= sColorFillScheme; |
1009 | 0 | if( rProp.Name == "SpPrLnSolidFillResolvedSchemeClr" ) |
1010 | 0 | rProp.Value >>= aResolvedColorFillScheme; |
1011 | 0 | else if( rProp.Name == "OriginalLnSolidFillClr" ) |
1012 | 0 | rProp.Value >>= nOriginalColor; |
1013 | 0 | else if( rProp.Name == "StyleLnRef" ) |
1014 | 0 | rProp.Value >>= aStyleProperties; |
1015 | 0 | else if( rProp.Name == "SpPrLnSolidFillSchemeClrTransformations" ) |
1016 | 0 | rProp.Value >>= aTransformations; |
1017 | 0 | else if( rProp.Name == "EmuLineWidth" ) |
1018 | 0 | rProp.Value >>= nEmuLineWidth; |
1019 | 0 | } |
1020 | 0 | for (const auto& rStyleProp : aStyleProperties) |
1021 | 0 | { |
1022 | 0 | if( rStyleProp.Name == "Color" ) |
1023 | 0 | rStyleProp.Value >>= nStyleColor; |
1024 | 0 | else if( rStyleProp.Name == "LineStyle" ) |
1025 | 0 | rStyleProp.Value >>= aStyleLineStyle; |
1026 | 0 | else if( rStyleProp.Name == "LineJoint" ) |
1027 | 0 | rStyleProp.Value >>= aStyleLineJoint; |
1028 | 0 | else if( rStyleProp.Name == "LineWidth" ) |
1029 | 0 | rStyleProp.Value >>= nStyleLineWidth; |
1030 | 0 | } |
1031 | 0 | } |
1032 | |
|
1033 | 0 | if (GetProperty(rXPropSet, u"LineWidth"_ustr)) |
1034 | 0 | mAny >>= nLineWidth; |
1035 | |
|
1036 | 0 | switch (aLineStyle) |
1037 | 0 | { |
1038 | 0 | case drawing::LineStyle_NONE: |
1039 | 0 | bNoFill = true; |
1040 | 0 | break; |
1041 | 0 | case drawing::LineStyle_DASH: |
1042 | 0 | if (GetProperty(rXPropSet, u"LineDash"_ustr)) |
1043 | 0 | { |
1044 | 0 | aLineDash = mAny.get<drawing::LineDash>(); |
1045 | | //this query is good for shapes, but in the case of charts it returns 0 values |
1046 | 0 | if (aLineDash.Dots == 0 && aLineDash.DotLen == 0 && aLineDash.Dashes == 0 && aLineDash.DashLen == 0 && aLineDash.Distance == 0) { |
1047 | 0 | OUString aLineDashName; |
1048 | 0 | if (GetProperty(rXPropSet, u"LineDashName"_ustr)) |
1049 | 0 | mAny >>= aLineDashName; |
1050 | 0 | if (!aLineDashName.isEmpty() && xModel) { |
1051 | 0 | css::uno::Any aAny = getLineDash(xModel, aLineDashName); |
1052 | 0 | aAny >>= aLineDash; |
1053 | 0 | } |
1054 | 0 | } |
1055 | 0 | } |
1056 | 0 | else |
1057 | 0 | { |
1058 | | //export the linestyle of chart wall (plot area) and chart page |
1059 | 0 | OUString aLineDashName; |
1060 | 0 | if (GetProperty(rXPropSet, u"LineDashName"_ustr)) |
1061 | 0 | mAny >>= aLineDashName; |
1062 | 0 | if (!aLineDashName.isEmpty() && xModel) { |
1063 | 0 | css::uno::Any aAny = getLineDash(xModel, aLineDashName); |
1064 | 0 | aAny >>= aLineDash; |
1065 | 0 | } |
1066 | 0 | } |
1067 | 0 | bDashSet = true; |
1068 | 0 | if (aLineDash.Style == DashStyle_ROUND || aLineDash.Style == DashStyle_ROUNDRELATIVE) |
1069 | 0 | { |
1070 | 0 | cap = "rnd"; |
1071 | 0 | } |
1072 | |
|
1073 | 0 | SAL_INFO("oox.shape", "dash dots: " << aLineDash.Dots << " dashes: " << aLineDash.Dashes |
1074 | 0 | << " dotlen: " << aLineDash.DotLen << " dashlen: " << aLineDash.DashLen << " distance: " << aLineDash.Distance); |
1075 | | |
1076 | 0 | [[fallthrough]]; |
1077 | 0 | case drawing::LineStyle_SOLID: |
1078 | 0 | default: |
1079 | 0 | if (GetProperty(rXPropSet, u"LineColor"_ustr)) |
1080 | 0 | { |
1081 | 0 | nColor = ::Color(ColorTransparency, mAny.get<sal_uInt32>() & 0xffffff); |
1082 | 0 | bColorSet = true; |
1083 | 0 | } |
1084 | 0 | if (GetProperty(rXPropSet, u"LineTransparence"_ustr)) |
1085 | 0 | { |
1086 | 0 | nColorAlpha = MAX_PERCENT - (mAny.get<sal_Int16>() * PER_PERCENT); |
1087 | 0 | } |
1088 | 0 | if (aLineCap == LineCap_ROUND) |
1089 | 0 | cap = "rnd"; |
1090 | 0 | else if (aLineCap == LineCap_SQUARE) |
1091 | 0 | cap = "sq"; |
1092 | 0 | break; |
1093 | 0 | } |
1094 | | |
1095 | | // if the line-width was not modified after importing then the original EMU value will be exported to avoid unexpected conversion (rounding) error |
1096 | 0 | if (nEmuLineWidth == 0 || static_cast<sal_uInt32>(oox::drawingml::convertEmuToHmm(nEmuLineWidth)) != nLineWidth) |
1097 | 0 | nEmuLineWidth = oox::drawingml::convertHmmToEmu(nLineWidth); |
1098 | 0 | mpFS->startElementNS( XML_a, XML_ln, |
1099 | 0 | XML_cap, cap, |
1100 | 0 | XML_w, sax_fastparser::UseIf(OString::number(nEmuLineWidth), |
1101 | 0 | nLineWidth == 0 || GetDocumentType() == DOCUMENT_XLSX // tdf#119565 LO doesn't export the actual theme.xml in XLSX. |
1102 | 0 | || (nLineWidth > 1 && nStyleLineWidth != nLineWidth))); |
1103 | |
|
1104 | 0 | if( bColorSet ) |
1105 | 0 | { |
1106 | 0 | if( nColor != nOriginalColor ) |
1107 | 0 | { |
1108 | | // the user has set a different color for the line |
1109 | 0 | if (!WriteSchemeColor(u"LineComplexColor"_ustr, rXPropSet)) |
1110 | 0 | WriteSolidFill(nColor, nColorAlpha); |
1111 | 0 | } |
1112 | 0 | else if( !sColorFillScheme.isEmpty() ) |
1113 | 0 | { |
1114 | | // the line had a scheme color and the user didn't change it |
1115 | 0 | WriteSolidFill( aResolvedColorFillScheme, aTransformations ); |
1116 | 0 | } |
1117 | 0 | else |
1118 | 0 | { |
1119 | 0 | WriteSolidFill( nColor, nColorAlpha ); |
1120 | 0 | } |
1121 | 0 | } |
1122 | |
|
1123 | 0 | if( bDashSet && aStyleLineStyle != drawing::LineStyle_DASH ) |
1124 | 0 | { |
1125 | | // Try to detect if it might come from ms preset line style import. |
1126 | | // MS Office styles are always relative, both binary and OOXML. |
1127 | | // "dot" is always the first dash and "dash" the second one. All OOXML presets linestyles |
1128 | | // start with the longer one. Definitions are in OOXML part 1, 20.1.10.49 |
1129 | | // The tests are strict, for to not catch styles from standard.sod (as of Aug 2019). |
1130 | 0 | bool bIsConverted = false; |
1131 | |
|
1132 | 0 | bool bIsRelative(aLineDash.Style == DashStyle_RECTRELATIVE || aLineDash.Style == DashStyle_ROUNDRELATIVE); |
1133 | 0 | if ( bIsRelative && aLineDash.Dots == 1) |
1134 | 0 | { // The length were tweaked on import in case of prstDash. Revert it here. |
1135 | 0 | sal_uInt32 nDotLen = aLineDash.DotLen; |
1136 | 0 | sal_uInt32 nDashLen = aLineDash.DashLen; |
1137 | 0 | sal_uInt32 nDistance = aLineDash.Distance; |
1138 | 0 | if (aLineCap != LineCap_BUTT && nDistance >= 99) |
1139 | 0 | { |
1140 | 0 | nDistance -= 99; |
1141 | 0 | nDotLen += 99; |
1142 | 0 | if (nDashLen > 0) |
1143 | 0 | nDashLen += 99; |
1144 | 0 | } |
1145 | | // LO uses length 0 for 100%, if the attribute is missing in ODF. |
1146 | | // Other applications might write 100%. Make is unique for the conditions. |
1147 | 0 | if (nDotLen == 0) |
1148 | 0 | nDotLen = 100; |
1149 | 0 | if (nDashLen == 0 && aLineDash.Dashes > 0) |
1150 | 0 | nDashLen = 100; |
1151 | 0 | bIsConverted = true; |
1152 | 0 | if (nDotLen == 100 && aLineDash.Dashes == 0 && nDashLen == 0 && nDistance == 300) |
1153 | 0 | { |
1154 | 0 | mpFS->singleElementNS(XML_a, XML_prstDash, XML_val, "dot"); |
1155 | 0 | } |
1156 | 0 | else if (nDotLen == 400 && aLineDash.Dashes == 0 && nDashLen == 0 && nDistance == 300) |
1157 | 0 | { |
1158 | 0 | mpFS->singleElementNS(XML_a, XML_prstDash, XML_val, "dash"); |
1159 | 0 | } |
1160 | 0 | else if (nDotLen == 400 && aLineDash.Dashes == 1 && nDashLen == 100 && nDistance == 300) |
1161 | 0 | { |
1162 | 0 | mpFS->singleElementNS(XML_a, XML_prstDash, XML_val, "dashDot"); |
1163 | 0 | } |
1164 | 0 | else if (nDotLen == 800 && aLineDash.Dashes == 0 && nDashLen == 0 && nDistance == 300) |
1165 | 0 | { |
1166 | 0 | mpFS->singleElementNS(XML_a, XML_prstDash, XML_val, "lgDash"); |
1167 | 0 | } |
1168 | 0 | else if (nDotLen == 800 && aLineDash.Dashes == 1 && nDashLen == 100 && nDistance == 300) |
1169 | 0 | { |
1170 | 0 | mpFS->singleElementNS(XML_a, XML_prstDash, XML_val, "lgDashDot"); |
1171 | 0 | } |
1172 | 0 | else if (nDotLen == 800 && aLineDash.Dashes == 2 && nDashLen == 100 && nDistance == 300) |
1173 | 0 | { |
1174 | 0 | mpFS->singleElementNS(XML_a, XML_prstDash, XML_val, "lgDashDotDot"); |
1175 | 0 | } |
1176 | 0 | else if (nDotLen == 100 && aLineDash.Dashes == 0 && nDashLen == 0 && nDistance == 100) |
1177 | 0 | { |
1178 | 0 | mpFS->singleElementNS(XML_a, XML_prstDash, XML_val, "sysDot"); |
1179 | 0 | } |
1180 | 0 | else if (nDotLen == 300 && aLineDash.Dashes == 0 && nDashLen == 0 && nDistance == 100) |
1181 | 0 | { |
1182 | 0 | mpFS->singleElementNS(XML_a, XML_prstDash, XML_val, "sysDash"); |
1183 | 0 | } |
1184 | 0 | else if (nDotLen == 300 && aLineDash.Dashes == 1 && nDashLen == 100 && nDistance == 100) |
1185 | 0 | { |
1186 | 0 | mpFS->singleElementNS(XML_a, XML_prstDash, XML_val, "sysDashDot"); |
1187 | 0 | } |
1188 | 0 | else if (nDotLen == 300 && aLineDash.Dashes == 2 && nDashLen == 100 && nDistance == 100) |
1189 | 0 | { |
1190 | 0 | mpFS->singleElementNS(XML_a, XML_prstDash, XML_val, "sysDashDotDot"); |
1191 | 0 | } |
1192 | 0 | else |
1193 | 0 | bIsConverted = false; |
1194 | 0 | } |
1195 | | // Do not map our own line styles to OOXML prstDash values, because custDash gives better results. |
1196 | 0 | if (!bIsConverted) |
1197 | 0 | { |
1198 | 0 | mpFS->startElementNS(XML_a, XML_custDash); |
1199 | | // In case of hairline we would need the current pixel size. Instead use a reasonable |
1200 | | // ersatz for it. The value is the same as SMALLEST_DASH_WIDTH in xattr.cxx. |
1201 | | // (And it makes sure fLineWidth is not zero in below division.) |
1202 | 0 | double fLineWidth = nLineWidth > 0 ? nLineWidth : 26.95; |
1203 | 0 | int i; |
1204 | 0 | double fSp = bIsRelative ? aLineDash.Distance : aLineDash.Distance * 100.0 / fLineWidth; |
1205 | | // LO uses line width, in case Distance is zero. MS Office would use a space of zero length. |
1206 | | // So set 100% explicitly. |
1207 | 0 | if (aLineDash.Distance <= 0) |
1208 | 0 | fSp = 100.0; |
1209 | | // In case of custDash, round caps are included in dash length in MS Office. Square caps are added |
1210 | | // to dash length, same as in ODF. Change the length values accordingly. |
1211 | 0 | if (aLineCap == LineCap_ROUND && fSp > 99.0) |
1212 | 0 | fSp -= 99.0; |
1213 | |
|
1214 | 0 | if (aLineDash.Dots > 0) |
1215 | 0 | { |
1216 | 0 | double fD = bIsRelative ? aLineDash.DotLen : aLineDash.DotLen * 100.0 / fLineWidth; |
1217 | | // LO sets length to 0, if attribute is missing in ODF. Then a relative length of 100% is intended. |
1218 | 0 | if (aLineDash.DotLen == 0) |
1219 | 0 | fD = 100.0; |
1220 | | // Tweak dash length, see above. |
1221 | 0 | if (aLineCap == LineCap_ROUND && fSp > 99.0) |
1222 | 0 | fD += 99.0; |
1223 | |
|
1224 | 0 | for( i = 0; i < aLineDash.Dots; i ++ ) |
1225 | 0 | { |
1226 | 0 | mpFS->singleElementNS( XML_a, XML_ds, |
1227 | 0 | XML_d , write1000thOfAPercent(fD), |
1228 | 0 | XML_sp, write1000thOfAPercent(fSp) ); |
1229 | 0 | } |
1230 | 0 | } |
1231 | 0 | if ( aLineDash.Dashes > 0 ) |
1232 | 0 | { |
1233 | 0 | double fD = bIsRelative ? aLineDash.DashLen : aLineDash.DashLen * 100.0 / fLineWidth; |
1234 | | // LO sets length to 0, if attribute is missing in ODF. Then a relative length of 100% is intended. |
1235 | 0 | if (aLineDash.DashLen == 0) |
1236 | 0 | fD = 100.0; |
1237 | | // Tweak dash length, see above. |
1238 | 0 | if (aLineCap == LineCap_ROUND && fSp > 99.0) |
1239 | 0 | fD += 99.0; |
1240 | |
|
1241 | 0 | for( i = 0; i < aLineDash.Dashes; i ++ ) |
1242 | 0 | { |
1243 | 0 | mpFS->singleElementNS( XML_a , XML_ds, |
1244 | 0 | XML_d , write1000thOfAPercent(fD), |
1245 | 0 | XML_sp, write1000thOfAPercent(fSp) ); |
1246 | 0 | } |
1247 | 0 | } |
1248 | |
|
1249 | 0 | SAL_WARN_IF(nLineWidth <= 0, |
1250 | 0 | "oox.shape", "while writing outline - custom dash - line width was < 0 : " << nLineWidth); |
1251 | 0 | SAL_WARN_IF(aLineDash.Dashes < 0, |
1252 | 0 | "oox.shape", "while writing outline - custom dash - number of dashes was < 0 : " << aLineDash.Dashes); |
1253 | 0 | SAL_WARN_IF(aLineDash.Dashes > 0 && aLineDash.DashLen <= 0, |
1254 | 0 | "oox.shape", "while writing outline - custom dash - dash length was < 0 : " << aLineDash.DashLen); |
1255 | 0 | SAL_WARN_IF(aLineDash.Dots < 0, |
1256 | 0 | "oox.shape", "while writing outline - custom dash - number of dots was < 0 : " << aLineDash.Dots); |
1257 | 0 | SAL_WARN_IF(aLineDash.Dots > 0 && aLineDash.DotLen <= 0, |
1258 | 0 | "oox.shape", "while writing outline - custom dash - dot length was < 0 : " << aLineDash.DotLen); |
1259 | 0 | SAL_WARN_IF(aLineDash.Distance <= 0, |
1260 | 0 | "oox.shape", "while writing outline - custom dash - distance was < 0 : " << aLineDash.Distance); |
1261 | | |
1262 | 0 | mpFS->endElementNS( XML_a, XML_custDash ); |
1263 | 0 | } |
1264 | 0 | } |
1265 | | |
1266 | 0 | if (!bNoFill && nLineWidth > 1 && GetProperty(rXPropSet, u"LineJoint"_ustr)) |
1267 | 0 | { |
1268 | 0 | LineJoint eLineJoint = mAny.get<LineJoint>(); |
1269 | | |
1270 | | // tdf#119565 LO doesn't export the actual theme.xml in XLSX. |
1271 | 0 | if (aStyleLineJoint == LineJoint_NONE || GetDocumentType() == DOCUMENT_XLSX |
1272 | 0 | || aStyleLineJoint != eLineJoint) |
1273 | 0 | { |
1274 | | // style-defined line joint does not exist, or is different from the shape's joint |
1275 | 0 | switch( eLineJoint ) |
1276 | 0 | { |
1277 | 0 | case LineJoint_NONE: |
1278 | 0 | case LineJoint_BEVEL: |
1279 | 0 | mpFS->singleElementNS(XML_a, XML_bevel); |
1280 | 0 | break; |
1281 | 0 | default: |
1282 | 0 | case LineJoint_MIDDLE: |
1283 | 0 | case LineJoint_MITER: |
1284 | 0 | mpFS->singleElementNS(XML_a, XML_miter); |
1285 | 0 | break; |
1286 | 0 | case LineJoint_ROUND: |
1287 | 0 | mpFS->singleElementNS(XML_a, XML_round); |
1288 | 0 | break; |
1289 | 0 | } |
1290 | 0 | } |
1291 | 0 | } |
1292 | | |
1293 | 0 | if( !bNoFill ) |
1294 | 0 | { |
1295 | 0 | WriteLineArrow( rXPropSet, true ); |
1296 | 0 | WriteLineArrow( rXPropSet, false ); |
1297 | 0 | } |
1298 | 0 | else |
1299 | 0 | { |
1300 | 0 | mpFS->singleElementNS(XML_a, XML_noFill); |
1301 | 0 | } |
1302 | |
|
1303 | 0 | mpFS->endElementNS( XML_a, XML_ln ); |
1304 | 0 | } |
1305 | | |
1306 | | OUString DrawingML::GetComponentDir() const |
1307 | 0 | { |
1308 | 0 | return OUString(getComponentDir(meDocumentType)); |
1309 | 0 | } |
1310 | | |
1311 | | OUString DrawingML::GetRelationCompPrefix() const |
1312 | 0 | { |
1313 | 0 | return OUString(getRelationCompPrefix(meDocumentType)); |
1314 | 0 | } |
1315 | | |
1316 | | void GraphicExport::writeSvgExtension(OUString const& rSvgRelId) |
1317 | 0 | { |
1318 | 0 | if (rSvgRelId.isEmpty()) |
1319 | 0 | return; |
1320 | | |
1321 | 0 | mpFS->startElementNS(XML_a, XML_extLst); |
1322 | 0 | mpFS->startElementNS(XML_a, XML_ext, XML_uri, "{96DAC541-7B7A-43D3-8B79-37D633B846F1}"); |
1323 | 0 | mpFS->singleElementNS(XML_asvg, XML_svgBlip, |
1324 | 0 | FSNS(XML_xmlns, XML_asvg), mpFilterBase->getNamespaceURL(OOX_NS(asvg)), |
1325 | 0 | FSNS(XML_r, XML_embed), rSvgRelId); |
1326 | 0 | mpFS->endElementNS(XML_a, XML_ext); |
1327 | 0 | mpFS->endElementNS( XML_a, XML_extLst); |
1328 | 0 | } |
1329 | | |
1330 | | void GraphicExport::writeBlip(Graphic const& rGraphic, std::vector<model::BlipEffect> const& rEffects) |
1331 | 0 | { |
1332 | 0 | OUString sRelId = writeToStorage(rGraphic, /*bRelPathToMedia*/false); |
1333 | |
|
1334 | 0 | mpFS->startElementNS(XML_a, XML_blip, FSNS(XML_r, XML_embed), sRelId); |
1335 | |
|
1336 | 0 | auto const& rVectorGraphicDataPtr = rGraphic.getVectorGraphicData(); |
1337 | |
|
1338 | 0 | if (rVectorGraphicDataPtr && rVectorGraphicDataPtr->getType() == VectorGraphicDataType::Svg) |
1339 | 0 | { |
1340 | 0 | OUString sSvgRelId = writeToStorage(rGraphic, /*bRelPathToMedia*/false, TypeHint::SVG); |
1341 | 0 | writeSvgExtension(sSvgRelId); |
1342 | 0 | } |
1343 | |
|
1344 | 0 | for (auto const& rEffect : rEffects) |
1345 | 0 | { |
1346 | 0 | switch (rEffect.meType) |
1347 | 0 | { |
1348 | 0 | case model::BlipEffectType::AlphaBiLevel: |
1349 | 0 | { |
1350 | 0 | mpFS->singleElementNS(XML_a, XML_alphaBiLevel, XML_thresh, OString::number(rEffect.mnThreshold)); |
1351 | 0 | } |
1352 | 0 | break; |
1353 | 0 | case model::BlipEffectType::AlphaCeiling: |
1354 | 0 | { |
1355 | 0 | mpFS->singleElementNS(XML_a, XML_alphaCeiling); |
1356 | 0 | } |
1357 | 0 | break; |
1358 | 0 | case model::BlipEffectType::AlphaFloor: |
1359 | 0 | { |
1360 | 0 | mpFS->singleElementNS(XML_a, XML_alphaFloor); |
1361 | 0 | } |
1362 | 0 | break; |
1363 | 0 | case model::BlipEffectType::AlphaInverse: |
1364 | 0 | { |
1365 | 0 | mpFS->singleElementNS(XML_a, XML_alphaInv); |
1366 | | // TODO: export rEffect.maColor1 |
1367 | 0 | } |
1368 | 0 | break; |
1369 | 0 | case model::BlipEffectType::AlphaModulate: |
1370 | 0 | { |
1371 | 0 | mpFS->singleElementNS(XML_a, XML_alphaMod); |
1372 | | // TODO |
1373 | 0 | } |
1374 | 0 | break; |
1375 | 0 | case model::BlipEffectType::AlphaModulateFixed: |
1376 | 0 | { |
1377 | 0 | mpFS->singleElementNS(XML_a, XML_alphaModFix, XML_amt, OString::number(rEffect.mnAmount)); |
1378 | 0 | } |
1379 | 0 | break; |
1380 | 0 | case model::BlipEffectType::AlphaReplace: |
1381 | 0 | { |
1382 | 0 | mpFS->singleElementNS(XML_a, XML_alphaRepl, XML_a, OString::number(rEffect.mnAlpha)); |
1383 | 0 | } |
1384 | 0 | break; |
1385 | 0 | case model::BlipEffectType::BiLevel: |
1386 | 0 | { |
1387 | 0 | mpFS->singleElementNS(XML_a, XML_biLevel, XML_thresh, OString::number(rEffect.mnThreshold)); |
1388 | 0 | } |
1389 | 0 | break; |
1390 | 0 | case model::BlipEffectType::Blur: |
1391 | 0 | { |
1392 | 0 | mpFS->singleElementNS(XML_a, XML_blur, |
1393 | 0 | XML_rad, OString::number(rEffect.mnRadius), |
1394 | 0 | XML_grow, rEffect.mbGrow ? "1" : "0"); |
1395 | 0 | } |
1396 | 0 | break; |
1397 | 0 | case model::BlipEffectType::ColorChange: |
1398 | 0 | { |
1399 | 0 | mpFS->startElementNS(XML_a, XML_clrChange, XML_useA, rEffect.mbUseAlpha ? "1" : "0"); |
1400 | 0 | mpFS->endElementNS(XML_a, XML_clrChange); |
1401 | 0 | } |
1402 | 0 | break; |
1403 | 0 | case model::BlipEffectType::ColorReplace: |
1404 | 0 | { |
1405 | 0 | mpFS->startElementNS(XML_a, XML_clrRepl); |
1406 | 0 | mpFS->endElementNS(XML_a, XML_clrRepl); |
1407 | 0 | } |
1408 | 0 | break; |
1409 | 0 | case model::BlipEffectType::DuoTone: |
1410 | 0 | { |
1411 | 0 | mpFS->startElementNS(XML_a, XML_duotone); |
1412 | 0 | mpFS->endElementNS(XML_a, XML_duotone); |
1413 | 0 | } |
1414 | 0 | break; |
1415 | 0 | case model::BlipEffectType::FillOverlay: |
1416 | 0 | { |
1417 | 0 | mpFS->singleElementNS(XML_a, XML_fillOverlay); |
1418 | 0 | } |
1419 | 0 | break; |
1420 | 0 | case model::BlipEffectType::Grayscale: |
1421 | 0 | { |
1422 | 0 | mpFS->singleElementNS(XML_a, XML_grayscl); |
1423 | 0 | } |
1424 | 0 | break; |
1425 | 0 | case model::BlipEffectType::HSL: |
1426 | 0 | { |
1427 | 0 | mpFS->singleElementNS(XML_a, XML_hsl, |
1428 | 0 | XML_hue, OString::number(rEffect.mnHue), |
1429 | 0 | XML_sat, OString::number(rEffect.mnSaturation), |
1430 | 0 | XML_lum, OString::number(rEffect.mnLuminance)); |
1431 | 0 | } |
1432 | 0 | break; |
1433 | 0 | case model::BlipEffectType::Luminance: |
1434 | 0 | { |
1435 | 0 | mpFS->singleElementNS(XML_a, XML_lum, |
1436 | 0 | XML_bright, OString::number(rEffect.mnBrightness), |
1437 | 0 | XML_contrast, OString::number(rEffect.mnContrast)); |
1438 | 0 | } |
1439 | 0 | break; |
1440 | 0 | case model::BlipEffectType::Tint: |
1441 | 0 | { |
1442 | 0 | mpFS->singleElementNS(XML_a, XML_tint, |
1443 | 0 | XML_hue, OString::number(rEffect.mnHue), |
1444 | 0 | XML_amt, OString::number(rEffect.mnAmount)); |
1445 | 0 | } |
1446 | 0 | break; |
1447 | | |
1448 | 0 | default: |
1449 | 0 | break; |
1450 | 0 | } |
1451 | 0 | } |
1452 | | |
1453 | 0 | mpFS->endElementNS(XML_a, XML_blip); |
1454 | 0 | } |
1455 | | |
1456 | | OUString GraphicExport::writeNewEntryToStorage(const Graphic& rGraphic, bool bRelPathToMedia) |
1457 | 0 | { |
1458 | 0 | GfxLink const aLink = rGraphic.GetGfxLink(); |
1459 | |
|
1460 | 0 | OUString sMediaType; |
1461 | 0 | OUString aExtension; |
1462 | |
|
1463 | 0 | SvMemoryStream aStream; |
1464 | 0 | const void* aData = aLink.GetData(); |
1465 | 0 | std::size_t nDataSize = aLink.GetDataSize(); |
1466 | |
|
1467 | 0 | switch (aLink.GetType()) |
1468 | 0 | { |
1469 | 0 | case GfxLinkType::NativeGif: |
1470 | 0 | sMediaType = u"image/gif"_ustr; |
1471 | 0 | aExtension = u"gif"_ustr; |
1472 | 0 | break; |
1473 | | |
1474 | | // #i15508# added BMP type for better exports |
1475 | | // export not yet active, so adding for reference (not checked) |
1476 | 0 | case GfxLinkType::NativeBmp: |
1477 | 0 | sMediaType = u"image/bmp"_ustr; |
1478 | 0 | aExtension = u"bmp"_ustr; |
1479 | 0 | break; |
1480 | | |
1481 | 0 | case GfxLinkType::NativeJpg: |
1482 | 0 | sMediaType = u"image/jpeg"_ustr; |
1483 | 0 | aExtension = u"jpeg"_ustr; |
1484 | 0 | break; |
1485 | 0 | case GfxLinkType::NativePng: |
1486 | 0 | sMediaType = u"image/png"_ustr; |
1487 | 0 | aExtension = u"png"_ustr; |
1488 | 0 | break; |
1489 | 0 | case GfxLinkType::NativeTif: |
1490 | 0 | sMediaType = u"image/tiff"_ustr; |
1491 | 0 | aExtension = u"tif"_ustr; |
1492 | 0 | break; |
1493 | 0 | case GfxLinkType::NativeWmf: |
1494 | 0 | sMediaType = u"image/x-wmf"_ustr; |
1495 | 0 | aExtension = u"wmf"_ustr; |
1496 | 0 | break; |
1497 | 0 | case GfxLinkType::NativeMet: |
1498 | 0 | sMediaType = u"image/x-met"_ustr; |
1499 | 0 | aExtension = u"met"_ustr; |
1500 | 0 | break; |
1501 | 0 | case GfxLinkType::NativePct: |
1502 | 0 | sMediaType = u"image/x-pict"_ustr; |
1503 | 0 | aExtension = u"pct"_ustr; |
1504 | 0 | break; |
1505 | 0 | case GfxLinkType::NativeMov: |
1506 | 0 | sMediaType = u"application/movie"_ustr; |
1507 | 0 | aExtension = u"MOV"_ustr; |
1508 | 0 | break; |
1509 | 0 | default: |
1510 | 0 | { |
1511 | 0 | GraphicType aType = rGraphic.GetType(); |
1512 | 0 | if (aType == GraphicType::Bitmap || aType == GraphicType::GdiMetafile) |
1513 | 0 | { |
1514 | 0 | if (aType == GraphicType::Bitmap) |
1515 | 0 | { |
1516 | 0 | (void)GraphicConverter::Export(aStream, rGraphic, ConvertDataFormat::PNG); |
1517 | 0 | sMediaType = u"image/png"_ustr; |
1518 | 0 | aExtension = u"png"_ustr; |
1519 | 0 | } |
1520 | 0 | else |
1521 | 0 | { |
1522 | 0 | (void)GraphicConverter::Export(aStream, rGraphic, ConvertDataFormat::EMF); |
1523 | 0 | sMediaType = u"image/x-emf"_ustr; |
1524 | 0 | aExtension = u"emf"_ustr; |
1525 | 0 | } |
1526 | 0 | } |
1527 | 0 | else |
1528 | 0 | { |
1529 | 0 | SAL_WARN("oox.shape", "unhandled graphic type " << static_cast<int>(aType)); |
1530 | | |
1531 | | /*Earlier, even in case of unhandled graphic types we were |
1532 | | proceeding to write the image, which would eventually |
1533 | | write an empty image with a zero size, and return a valid |
1534 | | relationID, which is incorrect. |
1535 | | */ |
1536 | 0 | return OUString(); |
1537 | 0 | } |
1538 | | |
1539 | 0 | aData = aStream.GetData(); |
1540 | 0 | nDataSize = aStream.GetEndOfData(); |
1541 | 0 | } |
1542 | 0 | break; |
1543 | 0 | } |
1544 | | |
1545 | 0 | GraphicExportCache& rGraphicExportCache = GraphicExportCache::get(); |
1546 | 0 | auto sImageCountString = OUString::number(rGraphicExportCache.nextImageCount()); |
1547 | |
|
1548 | 0 | OUString sComponentDir(getComponentDir(meDocumentType)); |
1549 | |
|
1550 | 0 | OUString sImagePath = sComponentDir + u"/media/image"_ustr + sImageCountString + u"."_ustr + aExtension; |
1551 | |
|
1552 | 0 | Reference<XOutputStream> xOutStream = mpFilterBase->openFragmentStream(sImagePath, sMediaType); |
1553 | 0 | xOutStream->writeBytes(Sequence<sal_Int8>(static_cast<const sal_Int8*>(aData), nDataSize)); |
1554 | 0 | xOutStream->closeOutput(); |
1555 | |
|
1556 | 0 | OUString sRelationCompPrefix; |
1557 | 0 | if (bRelPathToMedia) |
1558 | 0 | sRelationCompPrefix = u"../"_ustr; |
1559 | 0 | else |
1560 | 0 | sRelationCompPrefix = getRelationCompPrefix(meDocumentType); |
1561 | |
|
1562 | 0 | OUString sPath = sRelationCompPrefix + u"media/image"_ustr + sImageCountString + u"."_ustr + aExtension; |
1563 | |
|
1564 | 0 | rGraphicExportCache.addExportGraphics(rGraphic.GetChecksum(), sPath); |
1565 | |
|
1566 | 0 | return sPath; |
1567 | 0 | } |
1568 | | |
1569 | | namespace |
1570 | | { |
1571 | | BitmapChecksum makeChecksumUniqueForSVG(BitmapChecksum aChecksum) |
1572 | 0 | { |
1573 | | // need to modify the checksum so we know it's for SVG - just invert it |
1574 | 0 | return ~aChecksum; |
1575 | 0 | } |
1576 | | |
1577 | | } // end anonymous namespace |
1578 | | |
1579 | | OUString GraphicExport::writeNewSvgEntryToStorage(const Graphic& rGraphic, bool bRelPathToMedia) |
1580 | 0 | { |
1581 | 0 | OUString sMediaType = u"image/svg"_ustr; |
1582 | 0 | OUString aExtension = u"svg"_ustr; |
1583 | |
|
1584 | 0 | GfxLink const aLink = rGraphic.GetGfxLink(); |
1585 | 0 | if (aLink.GetType() != GfxLinkType::NativeSvg) |
1586 | 0 | return OUString(); |
1587 | | |
1588 | 0 | const void* aData = aLink.GetData(); |
1589 | 0 | std::size_t nDataSize = aLink.GetDataSize(); |
1590 | |
|
1591 | 0 | GraphicExportCache& rGraphicExportCache = GraphicExportCache::get(); |
1592 | 0 | auto sImageCountString = OUString::number(rGraphicExportCache.nextImageCount()); |
1593 | |
|
1594 | 0 | OUString sComponentDir(getComponentDir(meDocumentType)); |
1595 | |
|
1596 | 0 | OUString sImagePath = sComponentDir + u"/media/image"_ustr + sImageCountString + u"."_ustr + aExtension; |
1597 | |
|
1598 | 0 | Reference<XOutputStream> xOutStream = mpFilterBase->openFragmentStream(sImagePath, sMediaType); |
1599 | 0 | xOutStream->writeBytes(Sequence<sal_Int8>(static_cast<const sal_Int8*>(aData), nDataSize)); |
1600 | 0 | xOutStream->closeOutput(); |
1601 | |
|
1602 | 0 | OUString sRelationCompPrefix; |
1603 | 0 | if (bRelPathToMedia) |
1604 | 0 | sRelationCompPrefix = u"../"_ustr; |
1605 | 0 | else |
1606 | 0 | sRelationCompPrefix = getRelationCompPrefix(meDocumentType); |
1607 | |
|
1608 | 0 | OUString sPath = sRelationCompPrefix + u"media/image"_ustr + sImageCountString + u"."_ustr + aExtension; |
1609 | |
|
1610 | 0 | rGraphicExportCache.addExportGraphics(makeChecksumUniqueForSVG(rGraphic.GetChecksum()), sPath); |
1611 | |
|
1612 | 0 | return sPath; |
1613 | 0 | } |
1614 | | |
1615 | | OUString GraphicExport::writeToStorage(const Graphic& rGraphic, bool bRelPathToMedia, TypeHint eHint) |
1616 | 0 | { |
1617 | 0 | OUString sPath; |
1618 | |
|
1619 | 0 | auto aChecksum = rGraphic.GetChecksum(); |
1620 | 0 | if (eHint == TypeHint::SVG) |
1621 | 0 | aChecksum = makeChecksumUniqueForSVG(aChecksum); |
1622 | |
|
1623 | 0 | GraphicExportCache& rGraphicExportCache = GraphicExportCache::get(); |
1624 | 0 | sPath = rGraphicExportCache.findExportGraphics(aChecksum); |
1625 | |
|
1626 | 0 | if (sPath.isEmpty()) |
1627 | 0 | { |
1628 | 0 | if (eHint == TypeHint::SVG) |
1629 | 0 | sPath = writeNewSvgEntryToStorage(rGraphic, bRelPathToMedia); |
1630 | 0 | else |
1631 | 0 | sPath = writeNewEntryToStorage(rGraphic, bRelPathToMedia); |
1632 | |
|
1633 | 0 | if (sPath.isEmpty()) |
1634 | 0 | return OUString(); // couldn't store |
1635 | 0 | } |
1636 | | |
1637 | 0 | OUString sRelId = mpFilterBase->addRelation(mpFS->getOutputStream(), oox::getRelationship(Relationship::IMAGE), sPath); |
1638 | |
|
1639 | 0 | return sRelId; |
1640 | 0 | } |
1641 | | |
1642 | | std::shared_ptr<GraphicExport> DrawingML::createGraphicExport() |
1643 | 0 | { |
1644 | 0 | return std::make_shared<GraphicExport>(mpFS, mpFB, meDocumentType); |
1645 | 0 | } |
1646 | | |
1647 | | OUString DrawingML::writeGraphicToStorage(const Graphic& rGraphic , bool bRelPathToMedia, GraphicExport::TypeHint eHint) |
1648 | 0 | { |
1649 | 0 | GraphicExport aExporter(mpFS, mpFB, meDocumentType); |
1650 | 0 | return aExporter.writeToStorage(rGraphic, bRelPathToMedia, eHint); |
1651 | 0 | } |
1652 | | |
1653 | | void DrawingML::WriteMediaNonVisualProperties(const css::uno::Reference<css::drawing::XShape>& xShape) |
1654 | 0 | { |
1655 | 0 | SdrMediaObj* pMediaObj = dynamic_cast<SdrMediaObj*>(SdrObject::getSdrObjectFromXShape(xShape)); |
1656 | 0 | if (!pMediaObj) |
1657 | 0 | return; |
1658 | | |
1659 | | // extension |
1660 | 0 | OUString aExtension; |
1661 | 0 | const OUString& rURL(pMediaObj->getURL()); |
1662 | 0 | int nLastDot = rURL.lastIndexOf('.'); |
1663 | 0 | if (nLastDot >= 0) |
1664 | 0 | aExtension = rURL.copy(nLastDot).replace(':', '_'); // Colons are not allowed in Zip entry file names, see OStorageHelper::IsValidZipEntryFileName |
1665 | |
|
1666 | 0 | bool bEmbed = rURL.startsWith("vnd.sun.star.Package:"); |
1667 | 0 | Relationship eMediaType = Relationship::VIDEO; |
1668 | | |
1669 | | // mime type |
1670 | 0 | #if HAVE_FEATURE_AVMEDIA |
1671 | 0 | OUString aMimeType(pMediaObj->getMediaProperties().getMimeType()); |
1672 | | #else |
1673 | | OUString aMimeType("none"); |
1674 | | #endif |
1675 | 0 | if (aMimeType.startsWith("audio/")) |
1676 | 0 | { |
1677 | 0 | eMediaType = Relationship::AUDIO; |
1678 | 0 | } |
1679 | 0 | else |
1680 | 0 | if (aMimeType == "application/vnd.sun.star.media") |
1681 | 0 | { |
1682 | | // try to set something better |
1683 | | // TODO fix the importer to actually set the mimetype on import |
1684 | 0 | if (aExtension.equalsIgnoreAsciiCase(".avi")) |
1685 | 0 | aMimeType = "video/x-msvideo"; |
1686 | 0 | else if (aExtension.equalsIgnoreAsciiCase(".flv")) |
1687 | 0 | aMimeType = "video/x-flv"; |
1688 | 0 | else if (aExtension.equalsIgnoreAsciiCase(".mp4")) |
1689 | 0 | aMimeType = "video/mp4"; |
1690 | 0 | else if (aExtension.equalsIgnoreAsciiCase(".mov")) |
1691 | 0 | aMimeType = "video/quicktime"; |
1692 | 0 | else if (aExtension.equalsIgnoreAsciiCase(".ogv")) |
1693 | 0 | aMimeType = "video/ogg"; |
1694 | 0 | else if (aExtension.equalsIgnoreAsciiCase(".wmv")) |
1695 | 0 | aMimeType = "video/x-ms-wmv"; |
1696 | 0 | else if (aExtension.equalsIgnoreAsciiCase(".wav")) |
1697 | 0 | { |
1698 | 0 | aMimeType = "audio/x-wav"; |
1699 | 0 | eMediaType = Relationship::AUDIO; |
1700 | 0 | } |
1701 | 0 | else if (aExtension.equalsIgnoreAsciiCase(".m4a")) |
1702 | 0 | { |
1703 | 0 | aMimeType = "audio/mp4"; |
1704 | 0 | eMediaType = Relationship::AUDIO; |
1705 | 0 | } |
1706 | 0 | else if (aExtension.equalsIgnoreAsciiCase(".mp3")) |
1707 | 0 | { |
1708 | 0 | aMimeType = "audio/mp3"; |
1709 | 0 | eMediaType = Relationship::AUDIO; |
1710 | 0 | } |
1711 | 0 | } |
1712 | |
|
1713 | 0 | OUString aVideoFileRelId; |
1714 | 0 | OUString aMediaRelId; |
1715 | |
|
1716 | 0 | if (bEmbed) |
1717 | 0 | { |
1718 | 0 | sal_Int32 nImageCount = GraphicExportCache::get().nextImageCount(); |
1719 | |
|
1720 | 0 | OUString sFileName = GetComponentDir() + u"/media/media"_ustr + OUString::number(nImageCount) + aExtension; |
1721 | | |
1722 | | // copy the video stream |
1723 | 0 | Reference<XOutputStream> xOutStream = mpFB->openFragmentStream(sFileName, aMimeType); |
1724 | |
|
1725 | 0 | uno::Reference<io::XInputStream> xInputStream(pMediaObj->GetInputStream()); |
1726 | 0 | comphelper::OStorageHelper::CopyInputToOutput(xInputStream, xOutStream); |
1727 | |
|
1728 | 0 | xOutStream->closeOutput(); |
1729 | | |
1730 | | // create the relation |
1731 | 0 | OUString aPath = GetRelationCompPrefix() + u"media/media"_ustr + OUString::number(nImageCount) + aExtension; |
1732 | |
|
1733 | 0 | aVideoFileRelId = mpFB->addRelation(mpFS->getOutputStream(), oox::getRelationship(eMediaType), aPath); |
1734 | 0 | aMediaRelId = mpFB->addRelation(mpFS->getOutputStream(), oox::getRelationship(Relationship::MEDIA), aPath); |
1735 | 0 | } |
1736 | 0 | else |
1737 | 0 | { |
1738 | 0 | aVideoFileRelId = mpFB->addRelation(mpFS->getOutputStream(), oox::getRelationship(eMediaType), rURL, true); |
1739 | 0 | aMediaRelId = mpFB->addRelation(mpFS->getOutputStream(), oox::getRelationship(Relationship::MEDIA), rURL, true); |
1740 | 0 | } |
1741 | |
|
1742 | 0 | GetFS()->startElementNS(XML_p, XML_nvPr); |
1743 | |
|
1744 | 0 | GetFS()->singleElementNS(XML_a, eMediaType == Relationship::VIDEO ? XML_videoFile : XML_audioFile, |
1745 | 0 | FSNS(XML_r, XML_link), aVideoFileRelId); |
1746 | |
|
1747 | 0 | GetFS()->startElementNS(XML_p, XML_extLst); |
1748 | | // media extensions; google this ID for details |
1749 | 0 | GetFS()->startElementNS(XML_p, XML_ext, XML_uri, "{DAA4B4D4-6D71-4841-9C94-3DE7FCFB9230}"); |
1750 | |
|
1751 | 0 | GetFS()->singleElementNS(XML_p14, XML_media, |
1752 | 0 | bEmbed? FSNS(XML_r, XML_embed): FSNS(XML_r, XML_link), aMediaRelId); |
1753 | |
|
1754 | 0 | GetFS()->endElementNS(XML_p, XML_ext); |
1755 | 0 | GetFS()->endElementNS(XML_p, XML_extLst); |
1756 | |
|
1757 | 0 | GetFS()->endElementNS(XML_p, XML_nvPr); |
1758 | 0 | } |
1759 | | |
1760 | | void DrawingML::WriteImageBrightnessContrastTransparence(uno::Reference<beans::XPropertySet> const & rXPropSet) |
1761 | 0 | { |
1762 | 0 | sal_Int16 nBright = 0; |
1763 | 0 | sal_Int32 nContrast = 0; |
1764 | 0 | sal_Int32 nTransparence = 0; |
1765 | |
|
1766 | 0 | if (GetProperty(rXPropSet, u"AdjustLuminance"_ustr)) |
1767 | 0 | nBright = mAny.get<sal_Int16>(); |
1768 | 0 | if (GetProperty(rXPropSet, u"AdjustContrast"_ustr)) |
1769 | 0 | nContrast = mAny.get<sal_Int32>(); |
1770 | | // Used for shapes with picture fill |
1771 | 0 | if (GetProperty(rXPropSet, u"FillTransparence"_ustr)) |
1772 | 0 | nTransparence = mAny.get<sal_Int32>(); |
1773 | | // Used for pictures |
1774 | 0 | if (nTransparence == 0 && GetProperty(rXPropSet, u"Transparency"_ustr)) |
1775 | 0 | nTransparence = static_cast<sal_Int32>(mAny.get<sal_Int16>()); |
1776 | |
|
1777 | 0 | if (GetProperty(rXPropSet, u"GraphicColorMode"_ustr)) |
1778 | 0 | { |
1779 | 0 | drawing::ColorMode aColorMode; |
1780 | 0 | mAny >>= aColorMode; |
1781 | 0 | if (aColorMode == drawing::ColorMode_GREYS) |
1782 | 0 | mpFS->singleElementNS(XML_a, XML_grayscl); |
1783 | 0 | else if (aColorMode == drawing::ColorMode_MONO) |
1784 | | //black/white has a 0,5 threshold in LibreOffice |
1785 | 0 | mpFS->singleElementNS(XML_a, XML_biLevel, XML_thresh, OString::number(50000)); |
1786 | 0 | else if (aColorMode == drawing::ColorMode_WATERMARK) |
1787 | 0 | { |
1788 | | //map watermark with mso washout |
1789 | 0 | nBright = 70; |
1790 | 0 | nContrast = -70; |
1791 | 0 | } |
1792 | 0 | } |
1793 | | |
1794 | |
|
1795 | 0 | if (nBright || nContrast) |
1796 | 0 | { |
1797 | 0 | mpFS->singleElementNS(XML_a, XML_lum, |
1798 | 0 | XML_bright, sax_fastparser::UseIf(OString::number(nBright * 1000), nBright != 0), |
1799 | 0 | XML_contrast, sax_fastparser::UseIf(OString::number(nContrast * 1000), nContrast != 0)); |
1800 | 0 | } |
1801 | |
|
1802 | 0 | if (nTransparence) |
1803 | 0 | { |
1804 | 0 | sal_Int32 nAlphaMod = (100 - nTransparence ) * PER_PERCENT; |
1805 | 0 | mpFS->singleElementNS(XML_a, XML_alphaModFix, XML_amt, OString::number(nAlphaMod)); |
1806 | 0 | } |
1807 | 0 | } |
1808 | | |
1809 | | void DrawingML::WriteXGraphicBlip(uno::Reference<beans::XPropertySet> const & rXPropSet, |
1810 | | uno::Reference<graphic::XGraphic> const & rxGraphic, |
1811 | | bool bRelPathToMedia) |
1812 | 0 | { |
1813 | 0 | OUString sRelId; |
1814 | |
|
1815 | 0 | if (!rxGraphic.is()) |
1816 | 0 | return; |
1817 | | |
1818 | 0 | Graphic aGraphic(rxGraphic); |
1819 | |
|
1820 | 0 | sRelId = writeGraphicToStorage(aGraphic, bRelPathToMedia); |
1821 | |
|
1822 | 0 | mpFS->startElementNS(XML_a, XML_blip, FSNS(XML_r, XML_embed), sRelId); |
1823 | |
|
1824 | 0 | const auto& pVectorGraphicDataPtr = aGraphic.getVectorGraphicData(); |
1825 | |
|
1826 | 0 | if (pVectorGraphicDataPtr && pVectorGraphicDataPtr->getType() == VectorGraphicDataType::Svg) |
1827 | 0 | { |
1828 | 0 | GraphicExport aExporter(mpFS, mpFB, meDocumentType); |
1829 | 0 | OUString sSvgRelId = aExporter.writeToStorage(aGraphic, bRelPathToMedia, GraphicExport::TypeHint::SVG); |
1830 | 0 | if (!sSvgRelId.isEmpty()) |
1831 | 0 | aExporter.writeSvgExtension(sSvgRelId); |
1832 | 0 | } |
1833 | |
|
1834 | 0 | WriteImageBrightnessContrastTransparence(rXPropSet); |
1835 | |
|
1836 | 0 | WriteArtisticEffect(rXPropSet); |
1837 | |
|
1838 | 0 | mpFS->endElementNS(XML_a, XML_blip); |
1839 | 0 | } |
1840 | | |
1841 | | void DrawingML::WriteXGraphicBlipMode(uno::Reference<beans::XPropertySet> const & rXPropSet, |
1842 | | uno::Reference<graphic::XGraphic> const & rxGraphic, |
1843 | | css::awt::Size const& rSize) |
1844 | 0 | { |
1845 | 0 | BitmapMode eBitmapMode(BitmapMode_NO_REPEAT); |
1846 | 0 | if (GetProperty(rXPropSet, u"FillBitmapMode"_ustr)) |
1847 | 0 | mAny >>= eBitmapMode; |
1848 | |
|
1849 | 0 | SAL_INFO("oox.shape", "fill bitmap mode: " << int(eBitmapMode)); |
1850 | | |
1851 | 0 | switch (eBitmapMode) |
1852 | 0 | { |
1853 | 0 | case BitmapMode_REPEAT: |
1854 | 0 | WriteXGraphicTile(rXPropSet, rxGraphic, rSize); |
1855 | 0 | break; |
1856 | 0 | case BitmapMode_STRETCH: |
1857 | 0 | WriteXGraphicStretch(rXPropSet, rxGraphic); |
1858 | 0 | break; |
1859 | 0 | case BitmapMode_NO_REPEAT: |
1860 | 0 | WriteXGraphicCustomPosition(rXPropSet, rxGraphic, rSize); |
1861 | 0 | break; |
1862 | 0 | default: |
1863 | 0 | break; |
1864 | 0 | } |
1865 | 0 | } |
1866 | | |
1867 | | void DrawingML::WriteBlipOrNormalFill(const Reference<XPropertySet>& xPropSet, |
1868 | | const OUString& rURLPropName, const awt::Size& rSize) |
1869 | 0 | { |
1870 | | // check for blip and otherwise fall back to normal fill |
1871 | | // we always store normal fill properties but OOXML |
1872 | | // uses a choice between our fill props and BlipFill |
1873 | 0 | if (GetProperty ( xPropSet, rURLPropName )) |
1874 | 0 | WriteBlipFill( xPropSet, rURLPropName ); |
1875 | 0 | else |
1876 | 0 | WriteFill(xPropSet, rSize); |
1877 | 0 | } |
1878 | | |
1879 | | void DrawingML::WriteBlipFill(const Reference<XPropertySet>& rXPropSet, |
1880 | | const OUString& sURLPropName, const awt::Size& rSize) |
1881 | 0 | { |
1882 | 0 | WriteBlipFill( rXPropSet, rSize, sURLPropName, XML_a ); |
1883 | 0 | } |
1884 | | |
1885 | | void DrawingML::WriteBlipFill(const Reference<XPropertySet>& rXPropSet, const awt::Size& rSize, |
1886 | | const OUString& sURLPropName, sal_Int32 nXmlNamespace) |
1887 | 0 | { |
1888 | 0 | if ( !GetProperty( rXPropSet, sURLPropName ) ) |
1889 | 0 | return; |
1890 | | |
1891 | 0 | uno::Reference<graphic::XGraphic> xGraphic; |
1892 | 0 | if (mAny.has<uno::Reference<awt::XBitmap>>()) |
1893 | 0 | { |
1894 | 0 | uno::Reference<awt::XBitmap> xBitmap = mAny.get<uno::Reference<awt::XBitmap>>(); |
1895 | 0 | if (xBitmap.is()) |
1896 | 0 | xGraphic.set(xBitmap, uno::UNO_QUERY); |
1897 | 0 | } |
1898 | 0 | else if (mAny.has<uno::Reference<graphic::XGraphic>>()) |
1899 | 0 | { |
1900 | 0 | xGraphic = mAny.get<uno::Reference<graphic::XGraphic>>(); |
1901 | 0 | } |
1902 | |
|
1903 | 0 | if (xGraphic.is()) |
1904 | 0 | { |
1905 | 0 | bool bWriteMode = false; |
1906 | 0 | if (sURLPropName == "FillBitmap" || sURLPropName == "BackGraphic") |
1907 | 0 | bWriteMode = true; |
1908 | 0 | WriteXGraphicBlipFill(rXPropSet, xGraphic, nXmlNamespace, bWriteMode, false, rSize); |
1909 | 0 | } |
1910 | 0 | } |
1911 | | |
1912 | | void DrawingML::WriteXGraphicBlipFill(uno::Reference<beans::XPropertySet> const & rXPropSet, |
1913 | | uno::Reference<graphic::XGraphic> const & rxGraphic, |
1914 | | sal_Int32 nXmlNamespace, bool bWriteMode, |
1915 | | bool bRelPathToMedia, css::awt::Size const& rSize) |
1916 | 0 | { |
1917 | 0 | if (!rxGraphic.is() ) |
1918 | 0 | return; |
1919 | | |
1920 | 0 | mpFS->startElementNS(nXmlNamespace , XML_blipFill, XML_rotWithShape, "0"); |
1921 | |
|
1922 | 0 | WriteXGraphicBlip(rXPropSet, rxGraphic, bRelPathToMedia); |
1923 | |
|
1924 | 0 | if (GetDocumentType() != DOCUMENT_DOCX) |
1925 | 0 | { |
1926 | | // Write the crop rectangle of Impress as a source rectangle. |
1927 | 0 | WriteSrcRectXGraphic(rXPropSet, rxGraphic); |
1928 | 0 | } |
1929 | |
|
1930 | 0 | if (bWriteMode) |
1931 | 0 | { |
1932 | 0 | WriteXGraphicBlipMode(rXPropSet, rxGraphic, rSize); |
1933 | 0 | } |
1934 | 0 | else if(GetProperty(rXPropSet, u"FillBitmapStretch"_ustr)) |
1935 | 0 | { |
1936 | 0 | bool bStretch = mAny.get<bool>(); |
1937 | |
|
1938 | 0 | if (bStretch) |
1939 | 0 | { |
1940 | 0 | WriteXGraphicStretch(rXPropSet, rxGraphic); |
1941 | 0 | } |
1942 | 0 | } |
1943 | 0 | mpFS->endElementNS(nXmlNamespace, XML_blipFill); |
1944 | 0 | } |
1945 | | |
1946 | | void DrawingML::WritePattFill( const Reference< XPropertySet >& rXPropSet ) |
1947 | 0 | { |
1948 | 0 | if ( GetProperty( rXPropSet, u"FillHatch"_ustr ) ) |
1949 | 0 | { |
1950 | 0 | drawing::Hatch aHatch; |
1951 | 0 | mAny >>= aHatch; |
1952 | 0 | WritePattFill(rXPropSet, aHatch); |
1953 | 0 | } |
1954 | 0 | } |
1955 | | |
1956 | | void DrawingML::WritePattFill(const Reference<XPropertySet>& rXPropSet, const css::drawing::Hatch& rHatch) |
1957 | 0 | { |
1958 | 0 | mpFS->startElementNS(XML_a, XML_pattFill, XML_prst, GetHatchPattern(rHatch)); |
1959 | |
|
1960 | 0 | sal_Int32 nAlpha = MAX_PERCENT; |
1961 | 0 | if (GetProperty(rXPropSet, u"FillTransparence"_ustr)) |
1962 | 0 | { |
1963 | 0 | sal_Int32 nTransparency = 0; |
1964 | 0 | mAny >>= nTransparency; |
1965 | 0 | nAlpha = (MAX_PERCENT - (PER_PERCENT * nTransparency)); |
1966 | 0 | } |
1967 | |
|
1968 | 0 | mpFS->startElementNS(XML_a, XML_fgClr); |
1969 | 0 | WriteColor(::Color(ColorTransparency, rHatch.Color), nAlpha); |
1970 | 0 | mpFS->endElementNS( XML_a , XML_fgClr ); |
1971 | |
|
1972 | 0 | ::Color nColor = COL_WHITE; |
1973 | |
|
1974 | 0 | if ( GetProperty( rXPropSet, u"FillBackground"_ustr ) ) |
1975 | 0 | { |
1976 | 0 | bool isBackgroundFilled = false; |
1977 | 0 | mAny >>= isBackgroundFilled; |
1978 | 0 | if( isBackgroundFilled ) |
1979 | 0 | { |
1980 | 0 | if( GetProperty( rXPropSet, u"FillColor"_ustr ) ) |
1981 | 0 | { |
1982 | 0 | mAny >>= nColor; |
1983 | 0 | } |
1984 | 0 | } |
1985 | 0 | else |
1986 | 0 | nAlpha = 0; |
1987 | 0 | } |
1988 | |
|
1989 | 0 | mpFS->startElementNS(XML_a, XML_bgClr); |
1990 | 0 | WriteColor(nColor, nAlpha); |
1991 | 0 | mpFS->endElementNS( XML_a , XML_bgClr ); |
1992 | |
|
1993 | 0 | mpFS->endElementNS( XML_a , XML_pattFill ); |
1994 | 0 | } |
1995 | | |
1996 | | void DrawingML::WriteGraphicCropProperties(uno::Reference<beans::XPropertySet> const & rXPropSet, |
1997 | | Size const & rOriginalSize, |
1998 | | MapMode const & rMapMode) |
1999 | 0 | { |
2000 | 0 | if (!GetProperty(rXPropSet, u"GraphicCrop"_ustr)) |
2001 | 0 | return; |
2002 | | |
2003 | 0 | css::text::GraphicCrop aGraphicCropStruct; |
2004 | 0 | mAny >>= aGraphicCropStruct; |
2005 | |
|
2006 | 0 | if(GetProperty(rXPropSet, u"CustomShapeGeometry"_ustr)) |
2007 | 0 | { |
2008 | | // tdf#134210 GraphicCrop property is handled in import filter because of LibreOffice has not core |
2009 | | // feature. We cropped the bitmap physically and MSO shouldn't crop bitmap one more time. When we |
2010 | | // have core feature for graphic cropping in custom shapes, we should uncomment the code anymore. |
2011 | |
|
2012 | 0 | mpFS->singleElementNS( XML_a, XML_srcRect); |
2013 | 0 | } |
2014 | 0 | else |
2015 | 0 | { |
2016 | 0 | Size aOriginalSize(rOriginalSize); |
2017 | | |
2018 | | // GraphicCrop is in mm100, so in case the original size is in pixels, convert it over. |
2019 | 0 | if (rMapMode.GetMapUnit() == MapUnit::MapPixel) |
2020 | 0 | aOriginalSize = Application::GetDefaultDevice()->PixelToLogic(aOriginalSize, MapMode(MapUnit::Map100thMM)); |
2021 | |
|
2022 | 0 | if ( (0 != aGraphicCropStruct.Left) || (0 != aGraphicCropStruct.Top) || (0 != aGraphicCropStruct.Right) || (0 != aGraphicCropStruct.Bottom) ) |
2023 | 0 | { |
2024 | 0 | mpFS->singleElementNS( XML_a, XML_srcRect, |
2025 | 0 | XML_l, OString::number(rtl::math::round(aGraphicCropStruct.Left * 100000.0 / aOriginalSize.Width())), |
2026 | 0 | XML_t, OString::number(rtl::math::round(aGraphicCropStruct.Top * 100000.0 / aOriginalSize.Height())), |
2027 | 0 | XML_r, OString::number(rtl::math::round(aGraphicCropStruct.Right * 100000.0 / aOriginalSize.Width())), |
2028 | 0 | XML_b, OString::number(rtl::math::round(aGraphicCropStruct.Bottom * 100000.0 / aOriginalSize.Height())) ); |
2029 | 0 | } |
2030 | 0 | } |
2031 | 0 | } |
2032 | | |
2033 | | void DrawingML::WriteSrcRectXGraphic(uno::Reference<beans::XPropertySet> const & rxPropertySet, |
2034 | | uno::Reference<graphic::XGraphic> const & rxGraphic) |
2035 | 0 | { |
2036 | 0 | Graphic aGraphic(rxGraphic); |
2037 | 0 | Size aOriginalSize = aGraphic.GetPrefSize(); |
2038 | 0 | const MapMode aMapMode = aGraphic.GetPrefMapMode(); |
2039 | 0 | WriteGraphicCropProperties(rxPropertySet, aOriginalSize, aMapMode); |
2040 | 0 | } |
2041 | | |
2042 | | void DrawingML::WriteXGraphicStretch(uno::Reference<beans::XPropertySet> const & rXPropSet, |
2043 | | uno::Reference<graphic::XGraphic> const & rxGraphic) |
2044 | 0 | { |
2045 | 0 | if (GetDocumentType() != DOCUMENT_DOCX) |
2046 | 0 | { |
2047 | | // Limiting the area used for stretching is not supported in Impress. |
2048 | 0 | mpFS->singleElementNS(XML_a, XML_stretch); |
2049 | 0 | return; |
2050 | 0 | } |
2051 | | |
2052 | 0 | mpFS->startElementNS(XML_a, XML_stretch); |
2053 | |
|
2054 | 0 | bool bCrop = false; |
2055 | 0 | if (GetProperty(rXPropSet, u"GraphicCrop"_ustr)) |
2056 | 0 | { |
2057 | 0 | css::text::GraphicCrop aGraphicCropStruct; |
2058 | 0 | mAny >>= aGraphicCropStruct; |
2059 | |
|
2060 | 0 | if ((0 != aGraphicCropStruct.Left) |
2061 | 0 | || (0 != aGraphicCropStruct.Top) |
2062 | 0 | || (0 != aGraphicCropStruct.Right) |
2063 | 0 | || (0 != aGraphicCropStruct.Bottom)) |
2064 | 0 | { |
2065 | 0 | Graphic aGraphic(rxGraphic); |
2066 | 0 | Size aOriginalSize(aGraphic.GetPrefSize()); |
2067 | 0 | mpFS->singleElementNS(XML_a, XML_fillRect, |
2068 | 0 | XML_l, OString::number(((aGraphicCropStruct.Left) * 100000) / aOriginalSize.Width()), |
2069 | 0 | XML_t, OString::number(((aGraphicCropStruct.Top) * 100000) / aOriginalSize.Height()), |
2070 | 0 | XML_r, OString::number(((aGraphicCropStruct.Right) * 100000) / aOriginalSize.Width()), |
2071 | 0 | XML_b, OString::number(((aGraphicCropStruct.Bottom) * 100000) / aOriginalSize.Height())); |
2072 | 0 | bCrop = true; |
2073 | 0 | } |
2074 | 0 | } |
2075 | |
|
2076 | 0 | if (!bCrop) |
2077 | 0 | { |
2078 | 0 | mpFS->singleElementNS(XML_a, XML_fillRect); |
2079 | 0 | } |
2080 | |
|
2081 | 0 | mpFS->endElementNS(XML_a, XML_stretch); |
2082 | 0 | } |
2083 | | |
2084 | | static OUString lclConvertRectanglePointToToken(RectanglePoint eRectanglePoint) |
2085 | 0 | { |
2086 | 0 | OUString sAlignment; |
2087 | 0 | switch (eRectanglePoint) |
2088 | 0 | { |
2089 | 0 | case RectanglePoint_LEFT_TOP: |
2090 | 0 | sAlignment = "tl"; |
2091 | 0 | break; |
2092 | 0 | case RectanglePoint_MIDDLE_TOP: |
2093 | 0 | sAlignment = "t"; |
2094 | 0 | break; |
2095 | 0 | case RectanglePoint_RIGHT_TOP: |
2096 | 0 | sAlignment = "tr"; |
2097 | 0 | break; |
2098 | 0 | case RectanglePoint_LEFT_MIDDLE: |
2099 | 0 | sAlignment = "l"; |
2100 | 0 | break; |
2101 | 0 | case RectanglePoint_MIDDLE_MIDDLE: |
2102 | 0 | sAlignment = "ctr"; |
2103 | 0 | break; |
2104 | 0 | case RectanglePoint_RIGHT_MIDDLE: |
2105 | 0 | sAlignment = "r"; |
2106 | 0 | break; |
2107 | 0 | case RectanglePoint_LEFT_BOTTOM: |
2108 | 0 | sAlignment = "bl"; |
2109 | 0 | break; |
2110 | 0 | case RectanglePoint_MIDDLE_BOTTOM: |
2111 | 0 | sAlignment = "b"; |
2112 | 0 | break; |
2113 | 0 | case RectanglePoint_RIGHT_BOTTOM: |
2114 | 0 | sAlignment = "br"; |
2115 | 0 | break; |
2116 | 0 | default: |
2117 | 0 | break; |
2118 | 0 | } |
2119 | 0 | return sAlignment; |
2120 | 0 | } |
2121 | | |
2122 | | void DrawingML::WriteXGraphicTile(uno::Reference<beans::XPropertySet> const& rXPropSet, |
2123 | | uno::Reference<graphic::XGraphic> const& rxGraphic, |
2124 | | css::awt::Size const& rSize) |
2125 | 0 | { |
2126 | 0 | Graphic aGraphic(rxGraphic); |
2127 | 0 | Size aOriginalSize(aGraphic.GetPrefSize()); |
2128 | 0 | const MapMode aMapMode = aGraphic.GetPrefMapMode(); |
2129 | | // if the original size is in pixel, convert it to mm100 |
2130 | 0 | if (aMapMode.GetMapUnit() == MapUnit::MapPixel) |
2131 | 0 | aOriginalSize = Application::GetDefaultDevice()->PixelToLogic(aOriginalSize, |
2132 | 0 | MapMode(MapUnit::Map100thMM)); |
2133 | 0 | sal_Int32 nSizeX = 0; |
2134 | 0 | sal_Int32 nOffsetX = 0; |
2135 | 0 | if (GetProperty(rXPropSet, u"FillBitmapSizeX"_ustr)) |
2136 | 0 | { |
2137 | 0 | mAny >>= nSizeX; |
2138 | 0 | if (GetProperty(rXPropSet, u"FillBitmapPositionOffsetX"_ustr)) |
2139 | 0 | { |
2140 | 0 | sal_Int32 nX = (nSizeX != 0) ? nSizeX : aOriginalSize.Width(); |
2141 | 0 | if (nX < 0 && rSize.Width > 0) |
2142 | 0 | nX = rSize.Width * std::abs(nX) / 100; |
2143 | 0 | nOffsetX = (*o3tl::doAccess<sal_Int32>(mAny)) * nX * 3.6; |
2144 | 0 | } |
2145 | | |
2146 | | // convert the X size of bitmap to a percentage |
2147 | 0 | if (nSizeX > 0) |
2148 | 0 | nSizeX = double(nSizeX) / aOriginalSize.Width() * 100000; |
2149 | 0 | else if (nSizeX < 0) |
2150 | 0 | nSizeX *= 1000; |
2151 | 0 | else |
2152 | 0 | nSizeX = 100000; |
2153 | 0 | } |
2154 | |
|
2155 | 0 | sal_Int32 nSizeY = 0; |
2156 | 0 | sal_Int32 nOffsetY = 0; |
2157 | 0 | if (GetProperty(rXPropSet, u"FillBitmapSizeY"_ustr)) |
2158 | 0 | { |
2159 | 0 | mAny >>= nSizeY; |
2160 | 0 | if (GetProperty(rXPropSet, u"FillBitmapPositionOffsetY"_ustr)) |
2161 | 0 | { |
2162 | 0 | sal_Int32 nY = (nSizeY != 0) ? nSizeY : aOriginalSize.Height(); |
2163 | 0 | if (nY < 0 && rSize.Height > 0) |
2164 | 0 | nY = rSize.Height * std::abs(nY) / 100; |
2165 | 0 | nOffsetY = (*o3tl::doAccess<sal_Int32>(mAny)) * nY * 3.6; |
2166 | 0 | } |
2167 | | |
2168 | | // convert the Y size of bitmap to a percentage |
2169 | 0 | if (nSizeY > 0) |
2170 | 0 | nSizeY = double(nSizeY) / aOriginalSize.Height() * 100000; |
2171 | 0 | else if (nSizeY < 0) |
2172 | 0 | nSizeY *= 1000; |
2173 | 0 | else |
2174 | 0 | nSizeY = 100000; |
2175 | 0 | } |
2176 | | |
2177 | | // if the "Scale" setting is checked in the images settings dialog. |
2178 | 0 | if (nSizeX < 0 && nSizeY < 0) |
2179 | 0 | { |
2180 | 0 | if (rSize.Width != 0 && rSize.Height != 0) |
2181 | 0 | { |
2182 | 0 | nSizeX = rSize.Width / double(aOriginalSize.Width()) * std::abs(nSizeX); |
2183 | 0 | nSizeY = rSize.Height / double(aOriginalSize.Height()) * std::abs(nSizeY); |
2184 | 0 | } |
2185 | 0 | else |
2186 | 0 | { |
2187 | 0 | nSizeX = std::abs(nSizeX); |
2188 | 0 | nSizeY = std::abs(nSizeY); |
2189 | 0 | } |
2190 | 0 | } |
2191 | |
|
2192 | 0 | OUString sRectanglePoint; |
2193 | 0 | if (GetProperty(rXPropSet, u"FillBitmapRectanglePoint"_ustr)) |
2194 | 0 | sRectanglePoint = lclConvertRectanglePointToToken(*o3tl::doAccess<RectanglePoint>(mAny)); |
2195 | |
|
2196 | 0 | mpFS->singleElementNS(XML_a, XML_tile, XML_tx, OUString::number(nOffsetX), XML_ty, |
2197 | 0 | OUString::number(nOffsetY), XML_sx, OUString::number(nSizeX), XML_sy, |
2198 | 0 | OUString::number(nSizeY), XML_algn, sRectanglePoint); |
2199 | 0 | } |
2200 | | |
2201 | | void DrawingML::WriteXGraphicCustomPosition(uno::Reference<beans::XPropertySet> const& rXPropSet, |
2202 | | uno::Reference<graphic::XGraphic> const& rxGraphic, |
2203 | | css::awt::Size const& rSize) |
2204 | 0 | { |
2205 | 0 | Graphic aGraphic(rxGraphic); |
2206 | 0 | Size aOriginalSize(aGraphic.GetPrefSize()); |
2207 | 0 | const MapMode aMapMode = aGraphic.GetPrefMapMode(); |
2208 | | // if the original size is in pixel, convert it to mm100 |
2209 | 0 | if (aMapMode.GetMapUnit() == MapUnit::MapPixel) |
2210 | 0 | aOriginalSize = Application::GetDefaultDevice()->PixelToLogic(aOriginalSize, |
2211 | 0 | MapMode(MapUnit::Map100thMM)); |
2212 | 0 | double nSizeX = 0; |
2213 | 0 | if (GetProperty(rXPropSet, u"FillBitmapSizeX"_ustr)) |
2214 | 0 | { |
2215 | 0 | mAny >>= nSizeX; |
2216 | 0 | if (nSizeX <= 0) |
2217 | 0 | { |
2218 | 0 | if (nSizeX == 0) |
2219 | 0 | nSizeX = aOriginalSize.Width(); |
2220 | 0 | else |
2221 | 0 | nSizeX /= 100; // percentage |
2222 | 0 | } |
2223 | 0 | } |
2224 | |
|
2225 | 0 | double nSizeY = 0; |
2226 | 0 | if (GetProperty(rXPropSet, u"FillBitmapSizeY"_ustr)) |
2227 | 0 | { |
2228 | 0 | mAny >>= nSizeY; |
2229 | 0 | if (nSizeY <= 0) |
2230 | 0 | { |
2231 | 0 | if (nSizeY == 0) |
2232 | 0 | nSizeY = aOriginalSize.Height(); |
2233 | 0 | else |
2234 | 0 | nSizeY /= 100; // percentage |
2235 | 0 | } |
2236 | 0 | } |
2237 | |
|
2238 | 0 | if (nSizeX < 0 && nSizeY < 0 && rSize.Width != 0 && rSize.Height != 0) |
2239 | 0 | { |
2240 | 0 | nSizeX = rSize.Width * std::abs(nSizeX); |
2241 | 0 | nSizeY = rSize.Height * std::abs(nSizeY); |
2242 | 0 | } |
2243 | |
|
2244 | 0 | sal_Int32 nL = 0, nT = 0, nR = 0, nB = 0; |
2245 | 0 | if (GetProperty(rXPropSet, u"FillBitmapRectanglePoint"_ustr) && rSize.Width != 0 && rSize.Height != 0) |
2246 | 0 | { |
2247 | 0 | sal_Int32 nWidth = (1 - (nSizeX / rSize.Width)) * 100000; |
2248 | 0 | sal_Int32 nHeight = (1 - (nSizeY / rSize.Height)) * 100000; |
2249 | |
|
2250 | 0 | switch (*o3tl::doAccess<RectanglePoint>(mAny)) |
2251 | 0 | { |
2252 | 0 | case RectanglePoint_LEFT_TOP: nR = nWidth; nB = nHeight; break; |
2253 | 0 | case RectanglePoint_RIGHT_TOP: nL = nWidth; nB = nHeight; break; |
2254 | 0 | case RectanglePoint_LEFT_BOTTOM: nR = nWidth; nT = nHeight; break; |
2255 | 0 | case RectanglePoint_RIGHT_BOTTOM: nL = nWidth; nT = nHeight; break; |
2256 | 0 | case RectanglePoint_LEFT_MIDDLE: nR = nWidth; nT = nB = nHeight / 2; break; |
2257 | 0 | case RectanglePoint_RIGHT_MIDDLE: nL = nWidth; nT = nB = nHeight / 2; break; |
2258 | 0 | case RectanglePoint_MIDDLE_TOP: nB = nHeight; nL = nR = nWidth / 2; break; |
2259 | 0 | case RectanglePoint_MIDDLE_BOTTOM: nT = nHeight; nL = nR = nWidth / 2; break; |
2260 | 0 | case RectanglePoint_MIDDLE_MIDDLE: nL = nR = nWidth / 2; nT = nB = nHeight / 2; break; |
2261 | 0 | default: break; |
2262 | 0 | } |
2263 | 0 | } |
2264 | | |
2265 | 0 | mpFS->startElementNS(XML_a, XML_stretch); |
2266 | |
|
2267 | 0 | mpFS->singleElementNS(XML_a, XML_fillRect, XML_l, |
2268 | 0 | sax_fastparser::UseIf(OString::number(nL), nL != 0), XML_t, |
2269 | 0 | sax_fastparser::UseIf(OString::number(nT), nT != 0), XML_r, |
2270 | 0 | sax_fastparser::UseIf(OString::number(nR), nR != 0), XML_b, |
2271 | 0 | sax_fastparser::UseIf(OString::number(nB), nB != 0)); |
2272 | |
|
2273 | 0 | mpFS->endElementNS(XML_a, XML_stretch); |
2274 | 0 | } |
2275 | | |
2276 | | namespace |
2277 | | { |
2278 | | bool IsTopGroupObj(const uno::Reference<drawing::XShape>& xShape) |
2279 | 0 | { |
2280 | 0 | SdrObject* pObject = SdrObject::getSdrObjectFromXShape(xShape); |
2281 | 0 | if (!pObject) |
2282 | 0 | return false; |
2283 | | |
2284 | 0 | if (pObject->getParentSdrObjectFromSdrObject()) |
2285 | 0 | return false; |
2286 | | |
2287 | 0 | return pObject->IsGroupObject(); |
2288 | 0 | } |
2289 | | } |
2290 | | |
2291 | | void DrawingML::WriteTransformation(const Reference< XShape >& xShape, const tools::Rectangle& rRect, |
2292 | | sal_Int32 nXmlNamespace, bool bFlipH, bool bFlipV, sal_Int32 nRotation, bool bIsGroupShape) |
2293 | 0 | { |
2294 | |
|
2295 | 0 | mpFS->startElementNS( nXmlNamespace, XML_xfrm, |
2296 | 0 | XML_flipH, sax_fastparser::UseIf("1", bFlipH), |
2297 | 0 | XML_flipV, sax_fastparser::UseIf("1", bFlipV), |
2298 | 0 | XML_rot, sax_fastparser::UseIf(OString::number(nRotation), nRotation % 21600000 != 0)); |
2299 | |
|
2300 | 0 | sal_Int32 nLeft = rRect.Left(); |
2301 | 0 | sal_Int32 nTop = rRect.Top(); |
2302 | 0 | if (GetDocumentType() == DOCUMENT_DOCX && !m_xParent.is()) |
2303 | 0 | { |
2304 | 0 | nLeft = 0; |
2305 | 0 | nTop = 0; |
2306 | 0 | } |
2307 | 0 | sal_Int32 nChildLeft = nLeft; |
2308 | 0 | sal_Int32 nChildTop = nTop; |
2309 | |
|
2310 | 0 | mpFS->singleElementNS(XML_a, XML_off, |
2311 | 0 | XML_x, OString::number(oox::drawingml::convertHmmToEmu(nLeft)), |
2312 | 0 | XML_y, OString::number(oox::drawingml::convertHmmToEmu(nTop))); |
2313 | 0 | mpFS->singleElementNS(XML_a, XML_ext, |
2314 | 0 | XML_cx, OString::number(oox::drawingml::convertHmmToEmu(rRect.GetWidth())), |
2315 | 0 | XML_cy, OString::number(oox::drawingml::convertHmmToEmu(rRect.GetHeight()))); |
2316 | |
|
2317 | 0 | if (bIsGroupShape && (GetDocumentType() != DOCUMENT_DOCX || IsTopGroupObj(xShape))) |
2318 | 0 | { |
2319 | 0 | mpFS->singleElementNS(XML_a, XML_chOff, |
2320 | 0 | XML_x, OString::number(oox::drawingml::convertHmmToEmu(nChildLeft)), |
2321 | 0 | XML_y, OString::number(oox::drawingml::convertHmmToEmu(nChildTop))); |
2322 | 0 | mpFS->singleElementNS(XML_a, XML_chExt, |
2323 | 0 | XML_cx, OString::number(oox::drawingml::convertHmmToEmu(rRect.GetWidth())), |
2324 | 0 | XML_cy, OString::number(oox::drawingml::convertHmmToEmu(rRect.GetHeight()))); |
2325 | 0 | } |
2326 | |
|
2327 | 0 | mpFS->endElementNS( nXmlNamespace, XML_xfrm ); |
2328 | 0 | } |
2329 | | |
2330 | | void DrawingML::WriteShapeTransformation( const Reference< XShape >& rXShape, sal_Int32 nXmlNamespace, bool bFlipH, bool bFlipV, bool bSuppressRotation, bool bSuppressFlipping, bool bFlippedBeforeRotation ) |
2331 | 0 | { |
2332 | 0 | SAL_INFO("oox.shape", "write shape transformation"); |
2333 | | |
2334 | 0 | Degree100 nRotation; |
2335 | 0 | Degree100 nCameraRotation; |
2336 | 0 | awt::Point aPos = rXShape->getPosition(); |
2337 | 0 | awt::Size aSize = rXShape->getSize(); |
2338 | |
|
2339 | 0 | bool bFlipHWrite = bFlipH && !bSuppressFlipping; |
2340 | 0 | bool bFlipVWrite = bFlipV && !bSuppressFlipping; |
2341 | 0 | bFlipH = bFlipH && !bFlippedBeforeRotation; |
2342 | 0 | bFlipV = bFlipV && !bFlippedBeforeRotation; |
2343 | |
|
2344 | 0 | if (GetDocumentType() == DOCUMENT_DOCX && m_xParent.is()) |
2345 | 0 | { |
2346 | 0 | awt::Point aParentPos = m_xParent->getPosition(); |
2347 | 0 | aPos.X -= aParentPos.X; |
2348 | 0 | aPos.Y -= aParentPos.Y; |
2349 | 0 | } |
2350 | |
|
2351 | 0 | if ( aSize.Width < 0 ) |
2352 | 0 | aSize.Width = 1000; |
2353 | 0 | if ( aSize.Height < 0 ) |
2354 | 0 | aSize.Height = 1000; |
2355 | 0 | if (!bSuppressRotation) |
2356 | 0 | { |
2357 | 0 | SdrObject* pShape = SdrObject::getSdrObjectFromXShape(rXShape); |
2358 | 0 | nRotation = pShape ? pShape->GetRotateAngle() : 0_deg100; |
2359 | 0 | if ( GetDocumentType() != DOCUMENT_DOCX ) |
2360 | 0 | { |
2361 | 0 | int faccos=bFlipV ? -1 : 1; |
2362 | 0 | int facsin=bFlipH ? -1 : 1; |
2363 | 0 | aPos.X-=(1-faccos*cos(toRadians(nRotation)))*aSize.Width/2-facsin*sin(toRadians(nRotation))*aSize.Height/2; |
2364 | 0 | aPos.Y-=(1-faccos*cos(toRadians(nRotation)))*aSize.Height/2+facsin*sin(toRadians(nRotation))*aSize.Width/2; |
2365 | 0 | } |
2366 | 0 | else if (m_xParent.is() && nRotation != 0_deg100) |
2367 | 0 | { |
2368 | | // Position for rotated shapes inside group is not set by DocxSdrExport. |
2369 | 0 | basegfx::B2DRange aRect(-aSize.Width / 2.0, -aSize.Height / 2.0, aSize.Width / 2.0, |
2370 | 0 | aSize.Height / 2.0); |
2371 | 0 | basegfx::B2DHomMatrix aRotateMatrix = |
2372 | 0 | basegfx::utils::createRotateB2DHomMatrix(toRadians(nRotation)); |
2373 | 0 | aRect.transform(aRotateMatrix); |
2374 | 0 | aPos.X += -aSize.Width / 2.0 - aRect.getMinX(); |
2375 | 0 | aPos.Y += -aSize.Height / 2.0 - aRect.getMinY(); |
2376 | 0 | } |
2377 | | |
2378 | | // The RotateAngle property's value is independent from any flipping, and that's exactly what we need here. |
2379 | 0 | uno::Reference<beans::XPropertySet> xPropertySet(rXShape, uno::UNO_QUERY); |
2380 | 0 | uno::Reference<beans::XPropertySetInfo> xPropertySetInfo = xPropertySet->getPropertySetInfo(); |
2381 | 0 | if (xPropertySetInfo->hasPropertyByName(u"RotateAngle"_ustr)) |
2382 | 0 | { |
2383 | 0 | sal_Int32 nTmp; |
2384 | 0 | if (xPropertySet->getPropertyValue(u"RotateAngle"_ustr) >>= nTmp) |
2385 | 0 | nRotation = Degree100(nTmp); |
2386 | 0 | } |
2387 | | |
2388 | | // As long as the support of MS Office 3D-features is rudimentary, we restore the original |
2389 | | // values from InteropGrabBag. This affects images and custom shapes. |
2390 | 0 | if (xPropertySetInfo->hasPropertyByName(UNO_NAME_MISC_OBJ_INTEROPGRABBAG)) |
2391 | 0 | { |
2392 | 0 | uno::Sequence<beans::PropertyValue> aGrabBagProps; |
2393 | 0 | xPropertySet->getPropertyValue(UNO_NAME_MISC_OBJ_INTEROPGRABBAG) >>= aGrabBagProps; |
2394 | 0 | auto p3DEffectProps = std::find_if( |
2395 | 0 | std::cbegin(aGrabBagProps), std::cend(aGrabBagProps), |
2396 | 0 | [](const PropertyValue& rProp) { return rProp.Name == "3DEffectProperties"; }); |
2397 | 0 | if (p3DEffectProps != std::cend(aGrabBagProps)) |
2398 | 0 | { |
2399 | 0 | uno::Sequence<beans::PropertyValue> a3DEffectProps; |
2400 | 0 | p3DEffectProps->Value >>= a3DEffectProps; |
2401 | | // We have imported a scene3d. |
2402 | 0 | if (rXShape->getShapeType() == "com.sun.star.drawing.CustomShape") |
2403 | 0 | { |
2404 | 0 | auto pMSORotation |
2405 | 0 | = std::find_if(std::cbegin(aGrabBagProps), std::cend(aGrabBagProps), |
2406 | 0 | [](const PropertyValue& rProp) { |
2407 | 0 | return rProp.Name == "mso-rotation-angle"; |
2408 | 0 | }); |
2409 | 0 | sal_Int32 nMSORotation = 0; |
2410 | 0 | if (pMSORotation != std::cend(aGrabBagProps)) |
2411 | 0 | pMSORotation->Value >>= nMSORotation; |
2412 | 0 | WriteTransformation( |
2413 | 0 | rXShape, |
2414 | 0 | tools::Rectangle(Point(aPos.X, aPos.Y), Size(aSize.Width, aSize.Height)), |
2415 | 0 | nXmlNamespace, bFlipHWrite, bFlipVWrite, nMSORotation); |
2416 | 0 | return; |
2417 | 0 | } |
2418 | 0 | if (rXShape->getShapeType() == "com.sun.star.drawing.GraphicObjectShape") |
2419 | 0 | { |
2420 | | // tdf#133037: restore original rotate angle of image before output |
2421 | 0 | auto pCameraProps = std::find_if( |
2422 | 0 | std::cbegin(a3DEffectProps), std::cend(a3DEffectProps), |
2423 | 0 | [](const PropertyValue& rProp) { return rProp.Name == "Camera"; }); |
2424 | 0 | if (pCameraProps != std::cend(a3DEffectProps)) |
2425 | 0 | { |
2426 | 0 | uno::Sequence<beans::PropertyValue> aCameraProps; |
2427 | 0 | pCameraProps->Value >>= aCameraProps; |
2428 | 0 | auto pZRotationProp = std::find_if( |
2429 | 0 | std::cbegin(aCameraProps), std::cend(aCameraProps), |
2430 | 0 | [](const PropertyValue& rProp) { return rProp.Name == "rotRev"; }); |
2431 | 0 | if (pZRotationProp != std::cend(aCameraProps)) |
2432 | 0 | { |
2433 | 0 | sal_Int32 nTmp = 0; |
2434 | 0 | pZRotationProp->Value >>= nTmp; |
2435 | 0 | nCameraRotation = NormAngle36000(Degree100(nTmp / -600)); |
2436 | | // FixMe tdf#160327. Vertical flip will be false. |
2437 | 0 | } |
2438 | 0 | } |
2439 | 0 | } |
2440 | 0 | } |
2441 | 0 | } |
2442 | 0 | } // end if (!bSuppressRotation) |
2443 | | |
2444 | | // OOXML flips shapes before rotating them. |
2445 | 0 | if(bFlipH != bFlipV) |
2446 | 0 | nRotation = 36000_deg100 - nRotation; |
2447 | |
|
2448 | 0 | WriteTransformation(rXShape, tools::Rectangle(Point(aPos.X, aPos.Y), Size(aSize.Width, aSize.Height)), nXmlNamespace, |
2449 | 0 | bFlipHWrite, bFlipVWrite, ExportRotateClockwisify(nRotation + nCameraRotation), IsGroupShape( rXShape )); |
2450 | 0 | } |
2451 | | |
2452 | | static OUString lcl_GetTarget(const css::uno::Reference<css::frame::XModel>& xModel, std::u16string_view rURL) |
2453 | 0 | { |
2454 | 0 | Reference<drawing::XDrawPagesSupplier> xDPS(xModel, uno::UNO_QUERY_THROW); |
2455 | 0 | Reference<drawing::XDrawPages> xDrawPages(xDPS->getDrawPages(), uno::UNO_SET_THROW); |
2456 | 0 | sal_uInt32 nPageCount = xDrawPages->getCount(); |
2457 | 0 | OUString sTarget; |
2458 | |
|
2459 | 0 | for (sal_uInt32 i = 0; i < nPageCount; ++i) |
2460 | 0 | { |
2461 | 0 | Reference<XDrawPage> xDrawPage; |
2462 | 0 | xDrawPages->getByIndex(i) >>= xDrawPage; |
2463 | 0 | Reference<container::XNamed> xNamed(xDrawPage, UNO_QUERY); |
2464 | 0 | if (!xNamed) |
2465 | 0 | continue; |
2466 | 0 | OUString sSlideName = "#" + xNamed->getName(); |
2467 | 0 | if (rURL == sSlideName) |
2468 | 0 | { |
2469 | 0 | sTarget = "slide" + OUString::number(i + 1) + ".xml"; |
2470 | 0 | break; |
2471 | 0 | } |
2472 | 0 | } |
2473 | 0 | if (sTarget.isEmpty()) |
2474 | 0 | { |
2475 | 0 | size_t nSplit = rURL.rfind(' '); |
2476 | 0 | if (nSplit != std::u16string_view::npos) |
2477 | 0 | sTarget = OUString::Concat("slide") + rURL.substr(nSplit + 1) + ".xml"; |
2478 | 0 | } |
2479 | |
|
2480 | 0 | return sTarget; |
2481 | 0 | } |
2482 | | |
2483 | | void DrawingML::WriteRunProperties( const Reference< XPropertySet >& rRun, bool bIsField, sal_Int32 nElement, |
2484 | | bool bCheckDirect,bool& rbOverridingCharHeight, sal_Int32& rnCharHeight, |
2485 | | sal_Int16 nScriptType, const Reference< XPropertySet >& rXShapePropSet) |
2486 | 0 | { |
2487 | 0 | Reference< XPropertySet > rXPropSet = rRun; |
2488 | 0 | Reference< XPropertyState > rXPropState( rRun, UNO_QUERY ); |
2489 | 0 | OUString usLanguage; |
2490 | 0 | PropertyState eState; |
2491 | 0 | bool bComplex = ( nScriptType == css::i18n::ScriptType::COMPLEX ); |
2492 | 0 | const char* bold = "0"; |
2493 | 0 | const char* italic = nullptr; |
2494 | 0 | const char* underline = nullptr; |
2495 | 0 | const char* strikeout = nullptr; |
2496 | 0 | const char* cap = nullptr; |
2497 | 0 | sal_Int32 nSize = 1800; |
2498 | 0 | sal_Int32 nCharEscapement = 0; |
2499 | 0 | sal_Int32 nCharKerning = 0; |
2500 | 0 | sal_Int32 nCharEscapementHeight = 0; |
2501 | |
|
2502 | 0 | if ( nElement == XML_endParaRPr && rbOverridingCharHeight ) |
2503 | 0 | { |
2504 | 0 | nSize = rnCharHeight; |
2505 | 0 | } |
2506 | 0 | else if (GetProperty(rXPropSet, u"CharHeight"_ustr)) |
2507 | 0 | { |
2508 | 0 | nSize = static_cast<sal_Int32>(100*(*o3tl::doAccess<float>(mAny))); |
2509 | 0 | if ( nElement == XML_rPr || nElement == XML_defRPr ) |
2510 | 0 | { |
2511 | 0 | rbOverridingCharHeight = true; |
2512 | 0 | rnCharHeight = nSize; |
2513 | 0 | } |
2514 | 0 | } |
2515 | |
|
2516 | 0 | if (GetProperty(rXPropSet, u"CharKerning"_ustr)) |
2517 | 0 | nCharKerning = static_cast<sal_Int32>(*o3tl::doAccess<sal_Int16>(mAny)); |
2518 | | /** While setting values in propertymap, |
2519 | | * CharKerning converted using GetTextSpacingPoint |
2520 | | * i.e set @ https://opengrok.libreoffice.org/xref/core/oox/source/drawingml/textcharacterproperties.cxx#129 |
2521 | | * therefore to get original value CharKerning need to be convert. |
2522 | | * https://opengrok.libreoffice.org/xref/core/oox/source/drawingml/drawingmltypes.cxx#95 |
2523 | | **/ |
2524 | 0 | nCharKerning = toTextSpacingPoint(nCharKerning); |
2525 | |
|
2526 | 0 | if ((bComplex && GetProperty(rXPropSet, u"CharWeightComplex"_ustr)) |
2527 | 0 | || GetProperty(rXPropSet, u"CharWeight"_ustr)) |
2528 | 0 | { |
2529 | 0 | if ( *o3tl::doAccess<float>(mAny) >= awt::FontWeight::SEMIBOLD ) |
2530 | 0 | bold = "1"; |
2531 | 0 | } |
2532 | |
|
2533 | 0 | if ((bComplex && GetProperty(rXPropSet, u"CharPostureComplex"_ustr)) |
2534 | 0 | || GetProperty(rXPropSet, u"CharPosture"_ustr)) |
2535 | 0 | switch ( *o3tl::doAccess<awt::FontSlant>(mAny) ) |
2536 | 0 | { |
2537 | 0 | case awt::FontSlant_OBLIQUE : |
2538 | 0 | case awt::FontSlant_ITALIC : |
2539 | 0 | italic = "1"; |
2540 | 0 | break; |
2541 | 0 | default: |
2542 | 0 | break; |
2543 | 0 | } |
2544 | | |
2545 | 0 | if ((bCheckDirect && GetPropertyAndState(rXPropSet, rXPropState, u"CharUnderline"_ustr, eState) |
2546 | 0 | && eState == beans::PropertyState_DIRECT_VALUE) |
2547 | 0 | || GetProperty(rXPropSet, u"CharUnderline"_ustr)) |
2548 | 0 | { |
2549 | 0 | switch ( *o3tl::doAccess<sal_Int16>(mAny) ) |
2550 | 0 | { |
2551 | 0 | case awt::FontUnderline::NONE : |
2552 | 0 | underline = "none"; |
2553 | 0 | break; |
2554 | 0 | case awt::FontUnderline::SINGLE : |
2555 | 0 | underline = "sng"; |
2556 | 0 | break; |
2557 | 0 | case awt::FontUnderline::DOUBLE : |
2558 | 0 | underline = "dbl"; |
2559 | 0 | break; |
2560 | 0 | case awt::FontUnderline::DOTTED : |
2561 | 0 | underline = "dotted"; |
2562 | 0 | break; |
2563 | 0 | case awt::FontUnderline::DASH : |
2564 | 0 | underline = "dash"; |
2565 | 0 | break; |
2566 | 0 | case awt::FontUnderline::LONGDASH : |
2567 | 0 | underline = "dashLong"; |
2568 | 0 | break; |
2569 | 0 | case awt::FontUnderline::DASHDOT : |
2570 | 0 | underline = "dotDash"; |
2571 | 0 | break; |
2572 | 0 | case awt::FontUnderline::DASHDOTDOT : |
2573 | 0 | underline = "dotDotDash"; |
2574 | 0 | break; |
2575 | 0 | case awt::FontUnderline::WAVE : |
2576 | 0 | underline = "wavy"; |
2577 | 0 | break; |
2578 | 0 | case awt::FontUnderline::DOUBLEWAVE : |
2579 | 0 | underline = "wavyDbl"; |
2580 | 0 | break; |
2581 | 0 | case awt::FontUnderline::BOLD : |
2582 | 0 | underline = "heavy"; |
2583 | 0 | break; |
2584 | 0 | case awt::FontUnderline::BOLDDOTTED : |
2585 | 0 | underline = "dottedHeavy"; |
2586 | 0 | break; |
2587 | 0 | case awt::FontUnderline::BOLDDASH : |
2588 | 0 | underline = "dashHeavy"; |
2589 | 0 | break; |
2590 | 0 | case awt::FontUnderline::BOLDLONGDASH : |
2591 | 0 | underline = "dashLongHeavy"; |
2592 | 0 | break; |
2593 | 0 | case awt::FontUnderline::BOLDDASHDOT : |
2594 | 0 | underline = "dotDashHeavy"; |
2595 | 0 | break; |
2596 | 0 | case awt::FontUnderline::BOLDDASHDOTDOT : |
2597 | 0 | underline = "dotDotDashHeavy"; |
2598 | 0 | break; |
2599 | 0 | case awt::FontUnderline::BOLDWAVE : |
2600 | 0 | underline = "wavyHeavy"; |
2601 | 0 | break; |
2602 | 0 | } |
2603 | 0 | } |
2604 | | |
2605 | 0 | if ((bCheckDirect && GetPropertyAndState(rXPropSet, rXPropState, u"CharStrikeout"_ustr, eState) |
2606 | 0 | && eState == beans::PropertyState_DIRECT_VALUE) |
2607 | 0 | || GetProperty(rXPropSet, u"CharStrikeout"_ustr)) |
2608 | 0 | { |
2609 | 0 | switch ( *o3tl::doAccess<sal_Int16>(mAny) ) |
2610 | 0 | { |
2611 | 0 | case awt::FontStrikeout::NONE : |
2612 | 0 | strikeout = "noStrike"; |
2613 | 0 | break; |
2614 | 0 | case awt::FontStrikeout::SINGLE : |
2615 | | // LibO supports further values of character |
2616 | | // strikeout, OOXML standard (20.1.10.78, |
2617 | | // ST_TextStrikeType) however specifies only |
2618 | | // 3 - single, double and none. Approximate |
2619 | | // the remaining ones by single strike (better |
2620 | | // some strike than none at all). |
2621 | | // TODO: figure out how to do this better |
2622 | 0 | case awt::FontStrikeout::BOLD : |
2623 | 0 | case awt::FontStrikeout::SLASH : |
2624 | 0 | case awt::FontStrikeout::X : |
2625 | 0 | strikeout = "sngStrike"; |
2626 | 0 | break; |
2627 | 0 | case awt::FontStrikeout::DOUBLE : |
2628 | 0 | strikeout = "dblStrike"; |
2629 | 0 | break; |
2630 | 0 | } |
2631 | 0 | } |
2632 | | |
2633 | 0 | bool bLang = false; |
2634 | 0 | switch(nScriptType) |
2635 | 0 | { |
2636 | 0 | case css::i18n::ScriptType::ASIAN: |
2637 | 0 | bLang = GetProperty(rXPropSet, u"CharLocaleAsian"_ustr); break; |
2638 | 0 | case css::i18n::ScriptType::COMPLEX: |
2639 | 0 | bLang = GetProperty(rXPropSet, u"CharLocaleComplex"_ustr); break; |
2640 | 0 | default: |
2641 | 0 | bLang = GetProperty(rXPropSet, u"CharLocale"_ustr); break; |
2642 | 0 | } |
2643 | | |
2644 | 0 | if (bLang) |
2645 | 0 | { |
2646 | 0 | css::lang::Locale aLocale; |
2647 | 0 | mAny >>= aLocale; |
2648 | 0 | LanguageTag aLanguageTag( aLocale); |
2649 | 0 | if (!aLanguageTag.isSystemLocale()) |
2650 | 0 | usLanguage = aLanguageTag.getBcp47MS(); |
2651 | 0 | } |
2652 | |
|
2653 | 0 | if (GetPropertyAndState(rXPropSet, rXPropState, u"CharEscapement"_ustr, eState) |
2654 | 0 | && eState == beans::PropertyState_DIRECT_VALUE) |
2655 | 0 | mAny >>= nCharEscapement; |
2656 | |
|
2657 | 0 | if (GetPropertyAndState(rXPropSet, rXPropState, u"CharEscapementHeight"_ustr, eState) |
2658 | 0 | && eState == beans::PropertyState_DIRECT_VALUE) |
2659 | 0 | mAny >>= nCharEscapementHeight; |
2660 | |
|
2661 | 0 | if (DFLT_ESC_AUTO_SUPER == nCharEscapement) |
2662 | 0 | { |
2663 | | // Raised by the differences between the ascenders (ascent = baseline to top of highest letter). |
2664 | | // The ascent is generally about 80% of the total font height. |
2665 | | // That is why DFLT_ESC_PROP (58) leads to 33% (DFLT_ESC_SUPER) |
2666 | 0 | nCharEscapement = .8 * (100 - nCharEscapementHeight); |
2667 | 0 | } |
2668 | 0 | else if (DFLT_ESC_AUTO_SUB == nCharEscapement) |
2669 | 0 | { |
2670 | | // Lowered by the differences between the descenders (descent = baseline to bottom of lowest letter). |
2671 | | // The descent is generally about 20% of the total font height. |
2672 | | // That is why DFLT_ESC_PROP (58) leads to 8% (DFLT_ESC_SUB) |
2673 | 0 | nCharEscapement = .2 * -(100 - nCharEscapementHeight); |
2674 | 0 | } |
2675 | |
|
2676 | 0 | if (nCharEscapement && nCharEscapementHeight) |
2677 | 0 | { |
2678 | 0 | nSize = (nSize * nCharEscapementHeight) / 100; |
2679 | | // MSO uses default ~58% size |
2680 | 0 | nSize = (nSize / 0.58); |
2681 | 0 | } |
2682 | |
|
2683 | 0 | if (GetProperty(rXPropSet, u"CharCaseMap"_ustr)) |
2684 | 0 | { |
2685 | 0 | switch ( *o3tl::doAccess<sal_Int16>(mAny) ) |
2686 | 0 | { |
2687 | 0 | case CaseMap::UPPERCASE : |
2688 | 0 | cap = "all"; |
2689 | 0 | break; |
2690 | 0 | case CaseMap::SMALLCAPS : |
2691 | 0 | cap = "small"; |
2692 | 0 | break; |
2693 | 0 | } |
2694 | 0 | } |
2695 | | |
2696 | 0 | mpFS->startElementNS( XML_a, nElement, |
2697 | 0 | XML_b, bold, |
2698 | 0 | XML_i, italic, |
2699 | 0 | XML_lang, sax_fastparser::UseIf(usLanguage, !usLanguage.isEmpty()), |
2700 | 0 | XML_sz, OString::number(nSize), |
2701 | | // For Condensed character spacing spc value is negative. |
2702 | 0 | XML_spc, sax_fastparser::UseIf(OString::number(nCharKerning), nCharKerning != 0), |
2703 | 0 | XML_strike, strikeout, |
2704 | 0 | XML_u, underline, |
2705 | 0 | XML_baseline, sax_fastparser::UseIf(OString::number(nCharEscapement*1000), nCharEscapement != 0), |
2706 | 0 | XML_cap, cap ); |
2707 | | |
2708 | | // Fontwork-shapes in LO have text outline and fill from shape stroke and shape fill |
2709 | | // PowerPoint has this as run properties |
2710 | 0 | if (IsFontworkShape(rXShapePropSet)) |
2711 | 0 | { |
2712 | 0 | WriteOutline(rXShapePropSet); |
2713 | 0 | WriteBlipOrNormalFill(rXShapePropSet, u"Graphic"_ustr); |
2714 | 0 | WriteShapeEffects(rXShapePropSet); |
2715 | 0 | } |
2716 | 0 | else |
2717 | 0 | { |
2718 | | // mso doesn't like text color to be placed after typeface |
2719 | 0 | if ((bCheckDirect && GetPropertyAndState(rXPropSet, rXPropState, u"CharColor"_ustr, eState) |
2720 | 0 | && eState == beans::PropertyState_DIRECT_VALUE) |
2721 | 0 | || GetProperty(rXPropSet, u"CharColor"_ustr)) |
2722 | 0 | { |
2723 | 0 | ::Color color( ColorTransparency, *o3tl::doAccess<sal_uInt32>(mAny) ); |
2724 | 0 | SAL_INFO("oox.shape", "run color: " << sal_uInt32(color) << " auto: " << sal_uInt32(COL_AUTO)); |
2725 | | |
2726 | | // WriteSolidFill() handles MAX_PERCENT as "no transparency". |
2727 | 0 | sal_Int32 nTransparency = MAX_PERCENT; |
2728 | 0 | if (rXPropSet->getPropertySetInfo()->hasPropertyByName(u"CharTransparence"_ustr)) |
2729 | 0 | { |
2730 | 0 | rXPropSet->getPropertyValue(u"CharTransparence"_ustr) >>= nTransparency; |
2731 | | // UNO scale is 0..100, OOXML scale is 0..100000; also UNO tracks transparency, OOXML |
2732 | | // tracks opacity. |
2733 | 0 | nTransparency = MAX_PERCENT - (nTransparency * PER_PERCENT); |
2734 | 0 | } |
2735 | |
|
2736 | 0 | bool bContoured = false; |
2737 | 0 | if (GetProperty(rXPropSet, u"CharContoured"_ustr)) |
2738 | 0 | bContoured = *o3tl::doAccess<bool>(mAny); |
2739 | | |
2740 | | // tdf#127696 If the CharContoured is true, then the text color is white and the outline color is the CharColor. |
2741 | 0 | if (bContoured) |
2742 | 0 | { |
2743 | 0 | mpFS->startElementNS(XML_a, XML_ln); |
2744 | 0 | if (color == COL_AUTO) |
2745 | 0 | { |
2746 | 0 | mbIsBackgroundDark ? WriteSolidFill(COL_WHITE) : WriteSolidFill(COL_BLACK); |
2747 | 0 | } |
2748 | 0 | else |
2749 | 0 | { |
2750 | 0 | color.SetAlpha(255); |
2751 | 0 | if (!WriteSchemeColor(u"CharComplexColor"_ustr, rXPropSet)) |
2752 | 0 | WriteSolidFill(color, nTransparency); |
2753 | 0 | } |
2754 | 0 | mpFS->endElementNS(XML_a, XML_ln); |
2755 | |
|
2756 | 0 | WriteSolidFill(COL_WHITE); |
2757 | 0 | } |
2758 | | // tdf#104219 In LibreOffice and MS Office, there are two types of colors: |
2759 | | // Automatic and Fixed. OOXML is setting automatic color, by not providing color. |
2760 | 0 | else if( color != COL_AUTO ) |
2761 | 0 | { |
2762 | 0 | color.SetAlpha(255); |
2763 | | // TODO: special handle embossed/engraved |
2764 | 0 | if (!WriteSchemeColor(u"CharComplexColor"_ustr, rXPropSet)) |
2765 | 0 | { |
2766 | 0 | WriteSolidFill(color, nTransparency); |
2767 | 0 | } |
2768 | 0 | } |
2769 | 0 | else if (GetDocumentType() == DOCUMENT_PPTX) |
2770 | 0 | { |
2771 | | // Resolve COL_AUTO for PPTX since MS Powerpoint doesn't have automatic colors. |
2772 | 0 | bool bIsTextBackgroundDark = mbIsBackgroundDark; |
2773 | 0 | if (rXShapePropSet.is() && GetProperty(rXShapePropSet, u"FillStyle"_ustr) |
2774 | 0 | && mAny.get<FillStyle>() != FillStyle_NONE |
2775 | 0 | && GetProperty(rXShapePropSet, u"FillColor"_ustr)) |
2776 | 0 | { |
2777 | 0 | ::Color aShapeFillColor(ColorTransparency, mAny.get<sal_uInt32>()); |
2778 | 0 | bIsTextBackgroundDark = aShapeFillColor.IsDark(); |
2779 | 0 | } |
2780 | |
|
2781 | 0 | if (bIsTextBackgroundDark) |
2782 | 0 | WriteSolidFill(COL_WHITE); |
2783 | 0 | else |
2784 | 0 | WriteSolidFill(COL_BLACK); |
2785 | 0 | } |
2786 | |
|
2787 | 0 | if (rXShapePropSet.is() && GetDocumentType() != DOCUMENT_DOCX) |
2788 | 0 | { |
2789 | 0 | mpFS->startElementNS(XML_a, XML_effectLst); |
2790 | 0 | WriteTextGlowEffect(rXShapePropSet); |
2791 | 0 | mpFS->endElementNS(XML_a, XML_effectLst); |
2792 | 0 | } |
2793 | 0 | } |
2794 | 0 | } |
2795 | | |
2796 | | // tdf#128096, exporting XML_highlight to docx already works fine, |
2797 | | // so make sure this code is only run when exporting to pptx, just in case |
2798 | 0 | if (GetDocumentType() == DOCUMENT_PPTX) |
2799 | 0 | { |
2800 | 0 | if (GetProperty(rXPropSet, u"CharBackColor"_ustr)) |
2801 | 0 | { |
2802 | 0 | ::Color color(ColorTransparency, *o3tl::doAccess<sal_uInt32>(mAny)); |
2803 | 0 | if( color != COL_AUTO ) |
2804 | 0 | { |
2805 | 0 | mpFS->startElementNS(XML_a, XML_highlight); |
2806 | 0 | WriteColor( color ); |
2807 | 0 | mpFS->endElementNS( XML_a, XML_highlight ); |
2808 | 0 | } |
2809 | 0 | } |
2810 | 0 | } |
2811 | |
|
2812 | 0 | if (underline |
2813 | 0 | && ((bCheckDirect |
2814 | 0 | && GetPropertyAndState(rXPropSet, rXPropState, u"CharUnderlineColor"_ustr, eState) |
2815 | 0 | && eState == beans::PropertyState_DIRECT_VALUE) |
2816 | 0 | || GetProperty(rXPropSet, u"CharUnderlineColor"_ustr))) |
2817 | 0 | { |
2818 | 0 | ::Color color(ColorTransparency, *o3tl::doAccess<sal_uInt32>(mAny)); |
2819 | | // if color is automatic, then we shouldn't write information about color but to take color from character |
2820 | 0 | if( color != COL_AUTO ) |
2821 | 0 | { |
2822 | 0 | mpFS->startElementNS(XML_a, XML_uFill); |
2823 | 0 | WriteSolidFill( color ); |
2824 | 0 | mpFS->endElementNS( XML_a, XML_uFill ); |
2825 | 0 | } |
2826 | 0 | else |
2827 | 0 | { |
2828 | 0 | mpFS->singleElementNS(XML_a, XML_uFillTx); |
2829 | 0 | } |
2830 | 0 | } |
2831 | |
|
2832 | 0 | if (GetProperty(rXPropSet, u"CharFontName"_ustr)) |
2833 | 0 | { |
2834 | 0 | const char* const pitch = nullptr; |
2835 | 0 | const char* const charset = nullptr; |
2836 | 0 | OUString usTypeface; |
2837 | |
|
2838 | 0 | mAny >>= usTypeface; |
2839 | |
|
2840 | 0 | if (!mbEmbedFonts || EmbeddedFontsHelper::isCommonFont(usTypeface)) |
2841 | 0 | { |
2842 | 0 | OUString aSubstName( GetSubsFontName( usTypeface, SubsFontFlags::ONLYONE | SubsFontFlags::MS ) ); |
2843 | 0 | if (!aSubstName.isEmpty()) |
2844 | 0 | usTypeface = aSubstName; |
2845 | 0 | } |
2846 | |
|
2847 | 0 | mpFS->singleElementNS( XML_a, XML_latin, |
2848 | 0 | XML_typeface, usTypeface, |
2849 | 0 | XML_pitchFamily, pitch, |
2850 | 0 | XML_charset, charset ); |
2851 | 0 | } |
2852 | |
|
2853 | 0 | if ((bComplex |
2854 | 0 | && (GetPropertyAndState(rXPropSet, rXPropState, u"CharFontNameComplex"_ustr, eState) |
2855 | 0 | && eState == beans::PropertyState_DIRECT_VALUE)) |
2856 | 0 | || (!bComplex |
2857 | 0 | && (GetPropertyAndState(rXPropSet, rXPropState, u"CharFontNameAsian"_ustr, eState) |
2858 | 0 | && eState == beans::PropertyState_DIRECT_VALUE))) |
2859 | 0 | { |
2860 | 0 | const char* const pitch = nullptr; |
2861 | 0 | const char* const charset = nullptr; |
2862 | 0 | OUString usTypeface; |
2863 | |
|
2864 | 0 | mAny >>= usTypeface; |
2865 | 0 | if (!mbEmbedFonts || EmbeddedFontsHelper::isCommonFont(usTypeface)) |
2866 | 0 | { |
2867 | 0 | OUString aSubstName( GetSubsFontName( usTypeface, SubsFontFlags::ONLYONE | SubsFontFlags::MS ) ); |
2868 | 0 | if (!aSubstName.isEmpty()) |
2869 | 0 | usTypeface = aSubstName; |
2870 | 0 | } |
2871 | 0 | mpFS->singleElementNS( XML_a, bComplex ? XML_cs : XML_ea, |
2872 | 0 | XML_typeface, usTypeface, |
2873 | 0 | XML_pitchFamily, pitch, |
2874 | 0 | XML_charset, charset ); |
2875 | 0 | } |
2876 | |
|
2877 | 0 | if( bIsField ) |
2878 | 0 | { |
2879 | 0 | Reference< XTextField > rXTextField; |
2880 | 0 | if (GetProperty(rXPropSet, u"TextField"_ustr)) |
2881 | 0 | mAny >>= rXTextField; |
2882 | 0 | if( rXTextField.is() ) |
2883 | 0 | rXPropSet.set( rXTextField, UNO_QUERY ); |
2884 | 0 | } |
2885 | | |
2886 | | // field properties starts here |
2887 | 0 | if (GetProperty(rXPropSet, u"URL"_ustr)) |
2888 | 0 | { |
2889 | 0 | OUString sURL; |
2890 | |
|
2891 | 0 | mAny >>= sURL; |
2892 | 0 | if (!sURL.isEmpty()) |
2893 | 0 | { |
2894 | 0 | if (!sURL.match("#action?jump=")) |
2895 | 0 | { |
2896 | 0 | bool bExtURL = URLTransformer().isExternalURL(sURL); |
2897 | 0 | sURL = bExtURL ? sURL : lcl_GetTarget(GetFB()->getModel(), sURL); |
2898 | |
|
2899 | 0 | OUString sRelId |
2900 | 0 | = mpFB->addRelation(mpFS->getOutputStream(), |
2901 | 0 | bExtURL ? oox::getRelationship(Relationship::HYPERLINK) |
2902 | 0 | : oox::getRelationship(Relationship::SLIDE), |
2903 | 0 | sURL, bExtURL); |
2904 | |
|
2905 | 0 | if (bExtURL) |
2906 | 0 | mpFS->singleElementNS(XML_a, XML_hlinkClick, FSNS(XML_r, XML_id), sRelId); |
2907 | 0 | else |
2908 | 0 | mpFS->singleElementNS(XML_a, XML_hlinkClick, FSNS(XML_r, XML_id), sRelId, |
2909 | 0 | XML_action, "ppaction://hlinksldjump"); |
2910 | 0 | } |
2911 | 0 | else |
2912 | 0 | { |
2913 | 0 | sal_Int32 nIndex = sURL.indexOf('='); |
2914 | 0 | std::u16string_view aDestination(sURL.subView(nIndex + 1)); |
2915 | 0 | mpFS->singleElementNS(XML_a, XML_hlinkClick, FSNS(XML_r, XML_id), "", XML_action, |
2916 | 0 | OUString::Concat("ppaction://hlinkshowjump?jump=") + aDestination); |
2917 | 0 | } |
2918 | 0 | } |
2919 | 0 | } |
2920 | 0 | mpFS->endElementNS( XML_a, nElement ); |
2921 | 0 | } |
2922 | | |
2923 | | OUString DrawingML::GetFieldValue( const css::uno::Reference< css::text::XTextRange >& rRun, bool& bIsURLField ) |
2924 | 0 | { |
2925 | 0 | Reference< XPropertySet > rXPropSet( rRun, UNO_QUERY ); |
2926 | 0 | OUString aFieldType, aFieldValue; |
2927 | |
|
2928 | 0 | if (GetProperty(rXPropSet, u"TextPortionType"_ustr)) |
2929 | 0 | { |
2930 | 0 | aFieldType = *o3tl::doAccess<OUString>(mAny); |
2931 | 0 | SAL_INFO("oox.shape", "field type: " << aFieldType); |
2932 | 0 | } |
2933 | | |
2934 | 0 | if( aFieldType == "TextField" ) |
2935 | 0 | { |
2936 | 0 | Reference< XTextField > rXTextField; |
2937 | 0 | if (GetProperty(rXPropSet, u"TextField"_ustr)) |
2938 | 0 | mAny >>= rXTextField; |
2939 | 0 | if( rXTextField.is() ) |
2940 | 0 | { |
2941 | 0 | rXPropSet.set( rXTextField, UNO_QUERY ); |
2942 | 0 | if( rXPropSet.is() ) |
2943 | 0 | { |
2944 | 0 | OUString aFieldKind( rXTextField->getPresentation( true ) ); |
2945 | 0 | SAL_INFO("oox.shape", "field kind: " << aFieldKind); |
2946 | 0 | if( aFieldKind == "Page" ) |
2947 | 0 | { |
2948 | 0 | aFieldValue = "slidenum"; |
2949 | 0 | } |
2950 | 0 | else if( aFieldKind == "Pages" ) |
2951 | 0 | { |
2952 | 0 | aFieldValue = "slidecount"; |
2953 | 0 | } |
2954 | 0 | else if( aFieldKind == "PageName" ) |
2955 | 0 | { |
2956 | 0 | aFieldValue = "slidename"; |
2957 | 0 | } |
2958 | 0 | else if( aFieldKind == "URL" ) |
2959 | 0 | { |
2960 | 0 | bIsURLField = true; |
2961 | 0 | if (GetProperty(rXPropSet, u"Representation"_ustr)) |
2962 | 0 | mAny >>= aFieldValue; |
2963 | |
|
2964 | 0 | } |
2965 | 0 | else if(aFieldKind == "Date") |
2966 | 0 | { |
2967 | 0 | sal_Int32 nNumFmt = -1; |
2968 | 0 | rXPropSet->getPropertyValue(UNO_TC_PROP_NUMFORMAT) >>= nNumFmt; |
2969 | 0 | aFieldValue = GetDatetimeTypeFromDate(static_cast<SvxDateFormat>(nNumFmt)); |
2970 | 0 | } |
2971 | 0 | else if(aFieldKind == "ExtTime") |
2972 | 0 | { |
2973 | 0 | sal_Int32 nNumFmt = -1; |
2974 | 0 | rXPropSet->getPropertyValue(UNO_TC_PROP_NUMFORMAT) >>= nNumFmt; |
2975 | 0 | aFieldValue = GetDatetimeTypeFromTime(static_cast<SvxTimeFormat>(nNumFmt)); |
2976 | 0 | } |
2977 | 0 | else if(aFieldKind == "ExtFile") |
2978 | 0 | { |
2979 | 0 | sal_Int32 nNumFmt = -1; |
2980 | 0 | rXPropSet->getPropertyValue(UNO_TC_PROP_FILE_FORMAT) >>= nNumFmt; |
2981 | 0 | switch(nNumFmt) |
2982 | 0 | { |
2983 | 0 | case 0: aFieldValue = "file"; // Path/File name |
2984 | 0 | break; |
2985 | 0 | case 1: aFieldValue = "file1"; // Path |
2986 | 0 | break; |
2987 | 0 | case 2: aFieldValue = "file2"; // File name without extension |
2988 | 0 | break; |
2989 | 0 | case 3: aFieldValue = "file3"; // File name with extension |
2990 | 0 | } |
2991 | 0 | } |
2992 | 0 | else if(aFieldKind == "Author") |
2993 | 0 | { |
2994 | 0 | aFieldValue = "author"; |
2995 | 0 | } |
2996 | 0 | } |
2997 | 0 | } |
2998 | 0 | } |
2999 | 0 | return aFieldValue; |
3000 | 0 | } |
3001 | | |
3002 | | OUString DrawingML::GetDatetimeTypeFromDate(SvxDateFormat eDate) |
3003 | 0 | { |
3004 | 0 | return GetDatetimeTypeFromDateTime(eDate, SvxTimeFormat::AppDefault); |
3005 | 0 | } |
3006 | | |
3007 | | OUString DrawingML::GetDatetimeTypeFromTime(SvxTimeFormat eTime) |
3008 | 0 | { |
3009 | 0 | return GetDatetimeTypeFromDateTime(SvxDateFormat::AppDefault, eTime); |
3010 | 0 | } |
3011 | | |
3012 | | OUString DrawingML::GetDatetimeTypeFromDateTime(SvxDateFormat eDate, SvxTimeFormat eTime) |
3013 | 0 | { |
3014 | 0 | OUString aDateField; |
3015 | 0 | switch (eDate) |
3016 | 0 | { |
3017 | 0 | case SvxDateFormat::StdSmall: |
3018 | 0 | case SvxDateFormat::A: |
3019 | 0 | aDateField = "datetime"; |
3020 | 0 | break; |
3021 | 0 | case SvxDateFormat::B: |
3022 | 0 | aDateField = "datetime1"; // 13/02/1996 |
3023 | 0 | break; |
3024 | 0 | case SvxDateFormat::C: |
3025 | 0 | aDateField = "datetime5"; |
3026 | 0 | break; |
3027 | 0 | case SvxDateFormat::D: |
3028 | 0 | aDateField = "datetime3"; // 13 February 1996 |
3029 | 0 | break; |
3030 | 0 | case SvxDateFormat::StdBig: |
3031 | 0 | case SvxDateFormat::E: |
3032 | 0 | case SvxDateFormat::F: |
3033 | 0 | aDateField = "datetime2"; |
3034 | 0 | break; |
3035 | 0 | default: |
3036 | 0 | break; |
3037 | 0 | } |
3038 | | |
3039 | 0 | OUString aTimeField; |
3040 | 0 | switch (eTime) |
3041 | 0 | { |
3042 | 0 | case SvxTimeFormat::Standard: |
3043 | 0 | case SvxTimeFormat::HH24_MM_SS: |
3044 | 0 | case SvxTimeFormat::HH24_MM_SS_00: |
3045 | 0 | aTimeField = "datetime11"; // 13:49:38 |
3046 | 0 | break; |
3047 | 0 | case SvxTimeFormat::HH24_MM: |
3048 | 0 | aTimeField = "datetime10"; // 13:49 |
3049 | 0 | break; |
3050 | 0 | case SvxTimeFormat::HH12_MM: |
3051 | 0 | case SvxTimeFormat::HH12_MM_AMPM: |
3052 | 0 | aTimeField = "datetime12"; // 01:49 PM |
3053 | 0 | break; |
3054 | 0 | case SvxTimeFormat::HH12_MM_SS: |
3055 | 0 | case SvxTimeFormat::HH12_MM_SS_AMPM: |
3056 | 0 | case SvxTimeFormat::HH12_MM_SS_00: |
3057 | 0 | case SvxTimeFormat::HH12_MM_SS_00_AMPM: |
3058 | 0 | aTimeField = "datetime13"; // 01:49:38 PM |
3059 | 0 | break; |
3060 | 0 | default: |
3061 | 0 | break; |
3062 | 0 | } |
3063 | | |
3064 | 0 | if (!aDateField.isEmpty() && aTimeField.isEmpty()) |
3065 | 0 | return aDateField; |
3066 | 0 | else if (!aTimeField.isEmpty() && aDateField.isEmpty()) |
3067 | 0 | return aTimeField; |
3068 | 0 | else if (!aDateField.isEmpty() && !aTimeField.isEmpty()) |
3069 | 0 | { |
3070 | 0 | if (aTimeField == "datetime11" || aTimeField == "datetime13") |
3071 | | // only datetime format that has Date and HH:MM:SS |
3072 | 0 | return u"datetime9"_ustr; // dd/mm/yyyy H:MM:SS |
3073 | 0 | else |
3074 | | // only datetime format that has Date and HH:MM |
3075 | 0 | return u"datetime8"_ustr; // dd/mm/yyyy H:MM |
3076 | 0 | } |
3077 | 0 | else |
3078 | 0 | return u""_ustr; |
3079 | 0 | } |
3080 | | |
3081 | | void DrawingML::WriteRun( const Reference< XTextRange >& rRun, |
3082 | | bool& rbOverridingCharHeight, sal_Int32& rnCharHeight, |
3083 | | const css::uno::Reference< css::beans::XPropertySet >& rXShapePropSet) |
3084 | 0 | { |
3085 | 0 | Reference< XPropertySet > rXPropSet( rRun, UNO_QUERY ); |
3086 | 0 | sal_Int16 nLevel = -1; |
3087 | 0 | if (GetProperty(rXPropSet, u"NumberingLevel"_ustr)) |
3088 | 0 | mAny >>= nLevel; |
3089 | |
|
3090 | 0 | bool bNumberingIsNumber = true; |
3091 | 0 | if (GetProperty(rXPropSet, u"NumberingIsNumber"_ustr)) |
3092 | 0 | mAny >>= bNumberingIsNumber; |
3093 | |
|
3094 | 0 | float nFontSize = -1; |
3095 | 0 | if (GetProperty(rXPropSet, u"CharHeight"_ustr)) |
3096 | 0 | mAny >>= nFontSize; |
3097 | |
|
3098 | 0 | bool bIsURLField = false; |
3099 | 0 | OUString sFieldValue = GetFieldValue( rRun, bIsURLField ); |
3100 | 0 | bool bWriteField = !( sFieldValue.isEmpty() || bIsURLField ); |
3101 | |
|
3102 | 0 | OUString sText = rRun->getString(); |
3103 | | |
3104 | | //if there is no text following the bullet, add a space after the bullet |
3105 | 0 | if (nLevel !=-1 && bNumberingIsNumber && sText.isEmpty() ) |
3106 | 0 | sText=" "; |
3107 | |
|
3108 | 0 | if ( bIsURLField ) |
3109 | 0 | sText = sFieldValue; |
3110 | |
|
3111 | 0 | if( sText.isEmpty()) |
3112 | 0 | { |
3113 | 0 | Reference< XPropertySet > xPropSet( rRun, UNO_QUERY ); |
3114 | |
|
3115 | 0 | try |
3116 | 0 | { |
3117 | 0 | if( !xPropSet.is() || !( xPropSet->getPropertyValue( u"PlaceholderText"_ustr ) >>= sText ) ) |
3118 | 0 | return; |
3119 | 0 | if( sText.isEmpty() ) |
3120 | 0 | return; |
3121 | 0 | } |
3122 | 0 | catch (const Exception &) |
3123 | 0 | { |
3124 | 0 | return; |
3125 | 0 | } |
3126 | 0 | } |
3127 | | |
3128 | 0 | if (sText == "\n") |
3129 | 0 | { |
3130 | | // Empty run? Do not forget to write the font size in case of pptx: |
3131 | 0 | if ((GetDocumentType() == DOCUMENT_PPTX) && (nFontSize != -1)) |
3132 | 0 | { |
3133 | 0 | mpFS->startElementNS(XML_a, XML_br); |
3134 | 0 | mpFS->singleElementNS(XML_a, XML_rPr, XML_sz, |
3135 | 0 | OString::number(nFontSize * 100)); |
3136 | 0 | mpFS->endElementNS(XML_a, XML_br); |
3137 | 0 | } |
3138 | 0 | else |
3139 | 0 | mpFS->singleElementNS(XML_a, XML_br); |
3140 | 0 | } |
3141 | 0 | else |
3142 | 0 | { |
3143 | 0 | if( bWriteField ) |
3144 | 0 | { |
3145 | 0 | OString sUUID(comphelper::xml::generateGUIDString()); |
3146 | 0 | mpFS->startElementNS( XML_a, XML_fld, |
3147 | 0 | XML_id, sUUID.getStr(), |
3148 | 0 | XML_type, sFieldValue ); |
3149 | 0 | } |
3150 | 0 | else |
3151 | 0 | { |
3152 | 0 | mpFS->startElementNS(XML_a, XML_r); |
3153 | 0 | } |
3154 | |
|
3155 | 0 | Reference< XPropertySet > xPropSet( rRun, uno::UNO_QUERY ); |
3156 | |
|
3157 | 0 | WriteRunProperties( xPropSet, bIsURLField, XML_rPr, true, rbOverridingCharHeight, rnCharHeight, GetScriptType(sText), rXShapePropSet); |
3158 | 0 | mpFS->startElementNS(XML_a, XML_t); |
3159 | 0 | mpFS->writeEscaped( sText ); |
3160 | 0 | mpFS->endElementNS( XML_a, XML_t ); |
3161 | |
|
3162 | 0 | if( bWriteField ) |
3163 | 0 | mpFS->endElementNS( XML_a, XML_fld ); |
3164 | 0 | else |
3165 | 0 | mpFS->endElementNS( XML_a, XML_r ); |
3166 | 0 | } |
3167 | 0 | } |
3168 | | |
3169 | | static OUString GetAutoNumType(SvxNumType nNumberingType, bool bSDot, bool bPBehind, bool bPBoth) |
3170 | 0 | { |
3171 | 0 | OUString sPrefixSuffix; |
3172 | |
|
3173 | 0 | if (bPBoth) |
3174 | 0 | sPrefixSuffix = "ParenBoth"; |
3175 | 0 | else if (bPBehind) |
3176 | 0 | sPrefixSuffix = "ParenR"; |
3177 | 0 | else if (bSDot) |
3178 | 0 | sPrefixSuffix = "Period"; |
3179 | |
|
3180 | 0 | switch( nNumberingType ) |
3181 | 0 | { |
3182 | 0 | case SVX_NUM_CHARS_UPPER_LETTER_N : |
3183 | 0 | case SVX_NUM_CHARS_UPPER_LETTER : |
3184 | 0 | return "alphaUc" + sPrefixSuffix; |
3185 | | |
3186 | 0 | case SVX_NUM_CHARS_LOWER_LETTER_N : |
3187 | 0 | case SVX_NUM_CHARS_LOWER_LETTER : |
3188 | 0 | return "alphaLc" + sPrefixSuffix; |
3189 | | |
3190 | 0 | case SVX_NUM_ROMAN_UPPER : |
3191 | 0 | return "romanUc" + sPrefixSuffix; |
3192 | | |
3193 | 0 | case SVX_NUM_ROMAN_LOWER : |
3194 | 0 | return "romanLc" + sPrefixSuffix; |
3195 | | |
3196 | 0 | case SVX_NUM_ARABIC : |
3197 | 0 | { |
3198 | 0 | if (sPrefixSuffix.isEmpty()) |
3199 | 0 | return u"arabicPlain"_ustr; |
3200 | 0 | else |
3201 | 0 | return "arabic" + sPrefixSuffix; |
3202 | 0 | } |
3203 | 0 | default: |
3204 | 0 | break; |
3205 | 0 | } |
3206 | | |
3207 | 0 | return OUString(); |
3208 | 0 | } |
3209 | | |
3210 | | void DrawingML::WriteParagraphNumbering(const Reference< XPropertySet >& rXPropSet, float fFirstCharHeight, sal_Int16 nLevel ) |
3211 | 0 | { |
3212 | 0 | if (nLevel < 0 || !GetProperty(rXPropSet, u"NumberingRules"_ustr)) |
3213 | 0 | return; |
3214 | | |
3215 | 0 | Reference< XIndexAccess > rXIndexAccess; |
3216 | |
|
3217 | 0 | if (!(mAny >>= rXIndexAccess) || nLevel >= rXIndexAccess->getCount()) |
3218 | 0 | return; |
3219 | | |
3220 | 0 | SAL_INFO("oox.shape", "numbering rules"); |
3221 | | |
3222 | 0 | Sequence<PropertyValue> aPropertySequence; |
3223 | 0 | rXIndexAccess->getByIndex(nLevel) >>= aPropertySequence; |
3224 | |
|
3225 | 0 | if (!aPropertySequence.hasElements()) |
3226 | 0 | return; |
3227 | | |
3228 | 0 | SvxNumType nNumberingType = SVX_NUM_NUMBER_NONE; |
3229 | 0 | bool bSDot = false; |
3230 | 0 | bool bPBehind = false; |
3231 | 0 | bool bPBoth = false; |
3232 | 0 | sal_Unicode aBulletChar = 0x2022; // a bullet |
3233 | 0 | awt::FontDescriptor aFontDesc; |
3234 | 0 | bool bHasFontDesc = false; |
3235 | 0 | uno::Reference<graphic::XGraphic> xGraphic; |
3236 | 0 | sal_Int16 nBulletRelSize = 0; |
3237 | 0 | sal_Int16 nStartWith = 1; |
3238 | 0 | ::Color nBulletColor; |
3239 | 0 | bool bHasBulletColor = false; |
3240 | 0 | awt::Size aGraphicSize; |
3241 | |
|
3242 | 0 | for (const PropertyValue& rPropValue : aPropertySequence) |
3243 | 0 | { |
3244 | 0 | OUString aPropName( rPropValue.Name ); |
3245 | 0 | SAL_INFO("oox.shape", "pro name: " << aPropName); |
3246 | 0 | if ( aPropName == "NumberingType" ) |
3247 | 0 | { |
3248 | 0 | nNumberingType = static_cast<SvxNumType>(*o3tl::doAccess<sal_Int16>(rPropValue.Value)); |
3249 | 0 | } |
3250 | 0 | else if ( aPropName == "Prefix" ) |
3251 | 0 | { |
3252 | 0 | if( *o3tl::doAccess<OUString>(rPropValue.Value) == ")") |
3253 | 0 | bPBoth = true; |
3254 | 0 | } |
3255 | 0 | else if ( aPropName == "Suffix" ) |
3256 | 0 | { |
3257 | 0 | auto s = o3tl::doAccess<OUString>(rPropValue.Value); |
3258 | 0 | if( *s == ".") |
3259 | 0 | bSDot = true; |
3260 | 0 | else if( *s == ")") |
3261 | 0 | bPBehind = true; |
3262 | 0 | } |
3263 | 0 | else if(aPropName == "BulletColor") |
3264 | 0 | { |
3265 | 0 | nBulletColor = ::Color(ColorTransparency, *o3tl::doAccess<sal_uInt32>(rPropValue.Value)); |
3266 | 0 | bHasBulletColor = true; |
3267 | 0 | } |
3268 | 0 | else if ( aPropName == "BulletChar" ) |
3269 | 0 | { |
3270 | 0 | aBulletChar = (*o3tl::doAccess<OUString>(rPropValue.Value))[ 0 ]; |
3271 | 0 | } |
3272 | 0 | else if ( aPropName == "BulletFont" ) |
3273 | 0 | { |
3274 | 0 | aFontDesc = *o3tl::doAccess<awt::FontDescriptor>(rPropValue.Value); |
3275 | 0 | bHasFontDesc = true; |
3276 | | |
3277 | | // Our numbullet dialog has set the wrong textencoding for our "StarSymbol" font, |
3278 | | // instead of a Unicode encoding the encoding RTL_TEXTENCODING_SYMBOL was used. |
3279 | | // Because there might exist a lot of damaged documents I added this two lines |
3280 | | // which fixes the bullet problem for the export. |
3281 | 0 | if ( aFontDesc.Name.equalsIgnoreAsciiCase("StarSymbol") ) |
3282 | 0 | aFontDesc.CharSet = RTL_TEXTENCODING_MS_1252; |
3283 | |
|
3284 | 0 | } |
3285 | 0 | else if ( aPropName == "BulletRelSize" ) |
3286 | 0 | { |
3287 | 0 | nBulletRelSize = *o3tl::doAccess<sal_Int16>(rPropValue.Value); |
3288 | 0 | } |
3289 | 0 | else if ( aPropName == "StartWith" ) |
3290 | 0 | { |
3291 | 0 | nStartWith = *o3tl::doAccess<sal_Int16>(rPropValue.Value); |
3292 | 0 | } |
3293 | 0 | else if (aPropName == "GraphicBitmap") |
3294 | 0 | { |
3295 | 0 | auto xBitmap = rPropValue.Value.get<uno::Reference<awt::XBitmap>>(); |
3296 | 0 | xGraphic.set(xBitmap, uno::UNO_QUERY); |
3297 | 0 | } |
3298 | 0 | else if ( aPropName == "GraphicSize" ) |
3299 | 0 | { |
3300 | 0 | aGraphicSize = *o3tl::doAccess<awt::Size>(rPropValue.Value); |
3301 | 0 | SAL_INFO("oox.shape", "graphic size: " << aGraphicSize.Width << "x" << aGraphicSize.Height); |
3302 | 0 | } |
3303 | 0 | } |
3304 | | |
3305 | 0 | if (nNumberingType == SVX_NUM_NUMBER_NONE) |
3306 | 0 | return; |
3307 | | |
3308 | 0 | Graphic aGraphic(xGraphic); |
3309 | 0 | if (xGraphic.is() && aGraphic.GetType() != GraphicType::NONE) |
3310 | 0 | { |
3311 | 0 | tools::Long nFirstCharHeightMm = TransformMetric(fFirstCharHeight * 100.f, FieldUnit::POINT, FieldUnit::MM); |
3312 | 0 | float fBulletSizeRel = aGraphicSize.Height / static_cast<float>(nFirstCharHeightMm) / OOX_BULLET_LIST_SCALE_FACTOR; |
3313 | |
|
3314 | 0 | OUString sRelationId; |
3315 | |
|
3316 | 0 | if (fBulletSizeRel < 1.0f) |
3317 | 0 | { |
3318 | | // Add padding to get the bullet point centered in PPT |
3319 | 0 | Size aDestSize(64, 64); |
3320 | 0 | float fBulletSizeRelX = fBulletSizeRel / aGraphicSize.Height * aGraphicSize.Width; |
3321 | 0 | tools::Long nPaddingX = std::max<tools::Long>(0, std::lround((aDestSize.Width() - fBulletSizeRelX * aDestSize.Width()) / 2.f)); |
3322 | 0 | tools::Long nPaddingY = std::lround((aDestSize.Height() - fBulletSizeRel * aDestSize.Height()) / 2.f); |
3323 | 0 | tools::Rectangle aDestRect(nPaddingX, nPaddingY, aDestSize.Width() - nPaddingX, aDestSize.Height() - nPaddingY); |
3324 | |
|
3325 | 0 | AlphaMask aMask(aDestSize); |
3326 | 0 | aMask.Erase(255); |
3327 | 0 | BitmapEx aSourceBitmap(aGraphic.GetBitmapEx()); |
3328 | 0 | aSourceBitmap.Scale(aDestRect.GetSize()); |
3329 | 0 | tools::Rectangle aSourceRect(Point(0, 0), aDestRect.GetSize()); |
3330 | 0 | BitmapEx aDestBitmap(Bitmap(aDestSize, vcl::PixelFormat::N24_BPP), aMask); |
3331 | 0 | aDestBitmap.CopyPixel(aDestRect, aSourceRect, aSourceBitmap); |
3332 | 0 | Graphic aDestGraphic(aDestBitmap); |
3333 | 0 | sRelationId = writeGraphicToStorage(aDestGraphic); |
3334 | 0 | fBulletSizeRel = 1.0f; |
3335 | 0 | } |
3336 | 0 | else |
3337 | 0 | { |
3338 | 0 | sRelationId = writeGraphicToStorage(aGraphic); |
3339 | 0 | } |
3340 | |
|
3341 | 0 | mpFS->singleElementNS( XML_a, XML_buSzPct, |
3342 | 0 | XML_val, OString::number(std::min<sal_Int32>(std::lround(100000.f * fBulletSizeRel), 400000))); |
3343 | 0 | mpFS->startElementNS(XML_a, XML_buBlip); |
3344 | 0 | mpFS->singleElementNS(XML_a, XML_blip, FSNS(XML_r, XML_embed), sRelationId); |
3345 | 0 | mpFS->endElementNS( XML_a, XML_buBlip ); |
3346 | 0 | } |
3347 | 0 | else |
3348 | 0 | { |
3349 | 0 | if(bHasBulletColor) |
3350 | 0 | { |
3351 | 0 | if (nBulletColor == COL_AUTO ) |
3352 | 0 | { |
3353 | 0 | nBulletColor = ::Color(ColorTransparency, mbIsBackgroundDark ? 0xffffff : 0x000000); |
3354 | 0 | } |
3355 | 0 | mpFS->startElementNS(XML_a, XML_buClr); |
3356 | 0 | WriteColor( nBulletColor ); |
3357 | 0 | mpFS->endElementNS( XML_a, XML_buClr ); |
3358 | 0 | } |
3359 | |
|
3360 | 0 | if( nBulletRelSize && nBulletRelSize != 100 ) |
3361 | 0 | mpFS->singleElementNS( XML_a, XML_buSzPct, |
3362 | 0 | XML_val, OString::number(std::clamp<sal_Int32>(1000*nBulletRelSize, 25000, 400000))); |
3363 | 0 | if( bHasFontDesc ) |
3364 | 0 | { |
3365 | 0 | if ( SVX_NUM_CHAR_SPECIAL == nNumberingType ) |
3366 | 0 | aBulletChar = SubstituteBullet( aBulletChar, aFontDesc ); |
3367 | 0 | mpFS->singleElementNS( XML_a, XML_buFont, |
3368 | 0 | XML_typeface, aFontDesc.Name, |
3369 | 0 | XML_charset, sax_fastparser::UseIf("2", aFontDesc.CharSet == awt::CharSet::SYMBOL)); |
3370 | 0 | } |
3371 | |
|
3372 | 0 | OUString aAutoNumType = GetAutoNumType( nNumberingType, bSDot, bPBehind, bPBoth ); |
3373 | |
|
3374 | 0 | if (!aAutoNumType.isEmpty()) |
3375 | 0 | { |
3376 | 0 | mpFS->singleElementNS(XML_a, XML_buAutoNum, |
3377 | 0 | XML_type, aAutoNumType, |
3378 | 0 | XML_startAt, sax_fastparser::UseIf(OString::number(nStartWith), nStartWith > 1)); |
3379 | 0 | } |
3380 | 0 | else |
3381 | 0 | { |
3382 | 0 | mpFS->singleElementNS(XML_a, XML_buChar, XML_char, OUString(aBulletChar)); |
3383 | 0 | } |
3384 | 0 | } |
3385 | 0 | } |
3386 | | |
3387 | | void DrawingML::WriteParagraphTabStops(const Reference<XPropertySet>& rXPropSet) |
3388 | 0 | { |
3389 | 0 | css::uno::Sequence<css::style::TabStop> aTabStops; |
3390 | 0 | if (GetProperty(rXPropSet, u"ParaTabStops"_ustr)) |
3391 | 0 | aTabStops = *o3tl::doAccess<css::uno::Sequence<css::style::TabStop>>(mAny); |
3392 | |
|
3393 | 0 | if (aTabStops.getLength() > 0) |
3394 | 0 | mpFS->startElementNS(XML_a, XML_tabLst); |
3395 | |
|
3396 | 0 | for (const css::style::TabStop& rTabStop : aTabStops) |
3397 | 0 | { |
3398 | 0 | OString sPosition = OString::number(GetPointFromCoordinate(rTabStop.Position)); |
3399 | 0 | OString sAlignment; |
3400 | 0 | switch (rTabStop.Alignment) |
3401 | 0 | { |
3402 | 0 | case css::style::TabAlign_DECIMAL: |
3403 | 0 | sAlignment = "dec"_ostr; |
3404 | 0 | break; |
3405 | 0 | case css::style::TabAlign_RIGHT: |
3406 | 0 | sAlignment = "r"_ostr; |
3407 | 0 | break; |
3408 | 0 | case css::style::TabAlign_CENTER: |
3409 | 0 | sAlignment = "ctr"_ostr; |
3410 | 0 | break; |
3411 | 0 | case css::style::TabAlign_LEFT: |
3412 | 0 | default: |
3413 | 0 | sAlignment = "l"_ostr; |
3414 | 0 | } |
3415 | 0 | mpFS->singleElementNS(XML_a, XML_tab, XML_algn, sAlignment, XML_pos, sPosition); |
3416 | 0 | } |
3417 | 0 | if (aTabStops.getLength() > 0) |
3418 | 0 | mpFS->endElementNS(XML_a, XML_tabLst); |
3419 | 0 | } |
3420 | | |
3421 | | bool DrawingML::IsGroupShape( const Reference< XShape >& rXShape ) |
3422 | 0 | { |
3423 | 0 | bool bRet = false; |
3424 | 0 | if ( rXShape.is() ) |
3425 | 0 | { |
3426 | 0 | uno::Reference<lang::XServiceInfo> xServiceInfo(rXShape, uno::UNO_QUERY_THROW); |
3427 | 0 | bRet = xServiceInfo->supportsService(u"com.sun.star.drawing.GroupShape"_ustr); |
3428 | 0 | } |
3429 | 0 | return bRet; |
3430 | 0 | } |
3431 | | |
3432 | | sal_Int32 DrawingML::getBulletMarginIndentation (const Reference< XPropertySet >& rXPropSet,sal_Int16 nLevel, std::u16string_view propName) |
3433 | 0 | { |
3434 | 0 | if (nLevel < 0 || !GetProperty(rXPropSet, u"NumberingRules"_ustr)) |
3435 | 0 | return 0; |
3436 | | |
3437 | 0 | Reference< XIndexAccess > rXIndexAccess; |
3438 | |
|
3439 | 0 | if (!(mAny >>= rXIndexAccess) || nLevel >= rXIndexAccess->getCount()) |
3440 | 0 | return 0; |
3441 | | |
3442 | 0 | SAL_INFO("oox.shape", "numbering rules"); |
3443 | | |
3444 | 0 | Sequence<PropertyValue> aPropertySequence; |
3445 | 0 | rXIndexAccess->getByIndex(nLevel) >>= aPropertySequence; |
3446 | |
|
3447 | 0 | if (!aPropertySequence.hasElements()) |
3448 | 0 | return 0; |
3449 | | |
3450 | 0 | for (const PropertyValue& rPropValue : aPropertySequence) |
3451 | 0 | { |
3452 | 0 | OUString aPropName( rPropValue.Name ); |
3453 | 0 | SAL_INFO("oox.shape", "pro name: " << aPropName); |
3454 | 0 | if ( aPropName == propName ) |
3455 | 0 | return *o3tl::doAccess<sal_Int32>(rPropValue.Value); |
3456 | 0 | } |
3457 | | |
3458 | 0 | return 0; |
3459 | 0 | } |
3460 | | |
3461 | | const char* DrawingML::GetAlignment( style::ParagraphAdjust nAlignment ) |
3462 | 0 | { |
3463 | 0 | const char* sAlignment = nullptr; |
3464 | |
|
3465 | 0 | switch( nAlignment ) |
3466 | 0 | { |
3467 | 0 | case style::ParagraphAdjust_CENTER: |
3468 | 0 | sAlignment = "ctr"; |
3469 | 0 | break; |
3470 | 0 | case style::ParagraphAdjust_RIGHT: |
3471 | 0 | sAlignment = "r"; |
3472 | 0 | break; |
3473 | 0 | case style::ParagraphAdjust_BLOCK: |
3474 | 0 | sAlignment = "just"; |
3475 | 0 | break; |
3476 | 0 | default: |
3477 | 0 | ; |
3478 | 0 | } |
3479 | | |
3480 | 0 | return sAlignment; |
3481 | 0 | } |
3482 | | |
3483 | | void DrawingML::WriteLinespacing(const LineSpacing& rSpacing, float fFirstCharHeight) |
3484 | 0 | { |
3485 | 0 | if( rSpacing.Mode == LineSpacingMode::PROP ) |
3486 | 0 | { |
3487 | 0 | mpFS->singleElementNS( XML_a, XML_spcPct, |
3488 | 0 | XML_val, OString::number(static_cast<sal_Int32>(rSpacing.Height)*1000)); |
3489 | 0 | } |
3490 | 0 | else if (rSpacing.Mode == LineSpacingMode::MINIMUM |
3491 | 0 | && fFirstCharHeight > o3tl::convert(rSpacing.Height, o3tl::Length::mm100, o3tl::Length::pt)) |
3492 | 0 | { |
3493 | | // 100% proportional line spacing = single line spacing |
3494 | 0 | mpFS->singleElementNS(XML_a, XML_spcPct, XML_val, |
3495 | 0 | OString::number(static_cast<sal_Int32>(100000))); |
3496 | 0 | } |
3497 | 0 | else |
3498 | 0 | { |
3499 | 0 | mpFS->singleElementNS( XML_a, XML_spcPts, |
3500 | 0 | XML_val, OString::number(toTextSpacingPoint(rSpacing.Height))); |
3501 | 0 | } |
3502 | 0 | } |
3503 | | |
3504 | | bool DrawingML::WriteParagraphProperties(const Reference<XTextContent>& rParagraph, float fFirstCharHeight, sal_Int32 nElement) |
3505 | 0 | { |
3506 | 0 | Reference< XPropertySet > rXPropSet( rParagraph, UNO_QUERY ); |
3507 | 0 | Reference< XPropertyState > rXPropState( rParagraph, UNO_QUERY ); |
3508 | 0 | PropertyState eState; |
3509 | |
|
3510 | 0 | if( !rXPropSet.is() || !rXPropState.is() ) |
3511 | 0 | return false; |
3512 | | |
3513 | 0 | sal_Int16 nLevel = -1; |
3514 | 0 | if (GetProperty(rXPropSet, u"NumberingLevel"_ustr)) |
3515 | 0 | mAny >>= nLevel; |
3516 | |
|
3517 | 0 | bool bWriteNumbering = true; |
3518 | 0 | bool bForceZeroIndent = false; |
3519 | 0 | if (mbPlaceholder) |
3520 | 0 | { |
3521 | 0 | Reference< text::XTextRange > xParaText(rParagraph, UNO_QUERY); |
3522 | 0 | if (xParaText) |
3523 | 0 | { |
3524 | 0 | bool bNumberingOnThisLevel = false; |
3525 | 0 | if (nLevel > -1) |
3526 | 0 | { |
3527 | 0 | Reference< XIndexAccess > xNumberingRules(rXPropSet->getPropertyValue(u"NumberingRules"_ustr), UNO_QUERY); |
3528 | 0 | const PropertyValues aNumRuleOfLevel = xNumberingRules->getByIndex(nLevel).get<PropertyValues>(); |
3529 | 0 | for (const PropertyValue& rRule : aNumRuleOfLevel) |
3530 | 0 | if (rRule.Name == "NumberingType" && rRule.Value.hasValue()) |
3531 | 0 | bNumberingOnThisLevel = rRule.Value.get<sal_uInt16>() != style::NumberingType::NUMBER_NONE; |
3532 | 0 | } |
3533 | |
|
3534 | 0 | const bool bIsNumberingVisible = rXPropSet->getPropertyValue(u"NumberingIsNumber"_ustr).get<bool>(); |
3535 | 0 | const bool bIsLineEmpty = !xParaText->getString().getLength(); |
3536 | |
|
3537 | 0 | bWriteNumbering = !bIsLineEmpty && bIsNumberingVisible && (nLevel != -1); |
3538 | 0 | bForceZeroIndent = (!bIsNumberingVisible || bIsLineEmpty || !bNumberingOnThisLevel); |
3539 | 0 | } |
3540 | |
|
3541 | 0 | } |
3542 | |
|
3543 | 0 | sal_Int16 nTmp = sal_Int16(style::ParagraphAdjust_LEFT); |
3544 | 0 | if (GetProperty(rXPropSet, u"ParaAdjust"_ustr)) |
3545 | 0 | mAny >>= nTmp; |
3546 | 0 | style::ParagraphAdjust nAlignment = static_cast<style::ParagraphAdjust>(nTmp); |
3547 | |
|
3548 | 0 | bool bHasLinespacing = false; |
3549 | 0 | LineSpacing aLineSpacing; |
3550 | 0 | if (GetPropertyAndState(rXPropSet, rXPropState, u"ParaLineSpacing"_ustr, eState) |
3551 | 0 | && (mAny >>= aLineSpacing) |
3552 | 0 | && (eState == beans::PropertyState_DIRECT_VALUE || |
3553 | | // only export if it differs from the default 100% line spacing |
3554 | 0 | aLineSpacing.Mode != LineSpacingMode::PROP || aLineSpacing.Height != 100)) |
3555 | 0 | bHasLinespacing = true; |
3556 | |
|
3557 | 0 | bool bRtl = false; |
3558 | 0 | if (GetProperty(rXPropSet, u"WritingMode"_ustr)) |
3559 | 0 | { |
3560 | 0 | sal_Int16 nWritingMode; |
3561 | 0 | if( ( mAny >>= nWritingMode ) && nWritingMode == text::WritingMode2::RL_TB ) |
3562 | 0 | { |
3563 | 0 | bRtl = true; |
3564 | 0 | } |
3565 | 0 | } |
3566 | |
|
3567 | 0 | sal_Int32 nParaLeftMargin = 0; |
3568 | 0 | sal_Int32 nParaFirstLineIndent = 0; |
3569 | |
|
3570 | 0 | if (GetProperty(rXPropSet, u"ParaLeftMargin"_ustr)) |
3571 | 0 | mAny >>= nParaLeftMargin; |
3572 | 0 | if (GetProperty(rXPropSet, u"ParaFirstLineIndent"_ustr)) |
3573 | 0 | mAny >>= nParaFirstLineIndent; |
3574 | |
|
3575 | 0 | sal_Int32 nParaTopMargin = 0; |
3576 | 0 | sal_Int32 nParaBottomMargin = 0; |
3577 | |
|
3578 | 0 | if (GetProperty(rXPropSet, u"ParaTopMargin"_ustr)) |
3579 | 0 | mAny >>= nParaTopMargin; |
3580 | 0 | if (GetProperty(rXPropSet, u"ParaBottomMargin"_ustr)) |
3581 | 0 | mAny >>= nParaBottomMargin; |
3582 | |
|
3583 | 0 | sal_Int32 nLeftMargin = getBulletMarginIndentation ( rXPropSet, nLevel,u"LeftMargin"); |
3584 | 0 | sal_Int32 nLineIndentation = getBulletMarginIndentation ( rXPropSet, nLevel,u"FirstLineOffset"); |
3585 | |
|
3586 | 0 | if (bWriteNumbering && !bForceZeroIndent) |
3587 | 0 | { |
3588 | 0 | if (!(nLevel != -1 |
3589 | 0 | || nAlignment != style::ParagraphAdjust_LEFT |
3590 | 0 | || bHasLinespacing)) |
3591 | 0 | return false; |
3592 | 0 | } |
3593 | | |
3594 | 0 | sal_Int32 nParaDefaultTabSize = 0; |
3595 | 0 | if (GetProperty(rXPropSet, u"ParaTabStopDefaultDistance"_ustr)) |
3596 | 0 | mAny >>= nParaDefaultTabSize; |
3597 | |
|
3598 | 0 | if (nParaLeftMargin) // For Paragraph |
3599 | 0 | mpFS->startElementNS( XML_a, nElement, |
3600 | 0 | XML_lvl, sax_fastparser::UseIf(OString::number(nLevel), nLevel > 0), |
3601 | 0 | XML_marL, sax_fastparser::UseIf(OString::number(oox::drawingml::convertHmmToEmu(nParaLeftMargin)), nParaLeftMargin > 0), |
3602 | 0 | XML_indent, sax_fastparser::UseIf(OString::number((bForceZeroIndent && nParaFirstLineIndent == 0) ? 0 : oox::drawingml::convertHmmToEmu(nParaFirstLineIndent)), (bForceZeroIndent || nParaFirstLineIndent != 0)), |
3603 | 0 | XML_algn, GetAlignment( nAlignment ), |
3604 | 0 | XML_defTabSz, sax_fastparser::UseIf(OString::number(oox::drawingml::convertHmmToEmu(nParaDefaultTabSize)), nParaDefaultTabSize > 0), |
3605 | 0 | XML_rtl, sax_fastparser::UseIf(ToPsz10(bRtl), bRtl)); |
3606 | 0 | else |
3607 | 0 | mpFS->startElementNS( XML_a, nElement, |
3608 | 0 | XML_lvl, sax_fastparser::UseIf(OString::number(nLevel), nLevel > 0), |
3609 | 0 | XML_marL, sax_fastparser::UseIf(OString::number(oox::drawingml::convertHmmToEmu(nLeftMargin)), nLeftMargin > 0), |
3610 | 0 | XML_indent, sax_fastparser::UseIf(OString::number(!bForceZeroIndent ? oox::drawingml::convertHmmToEmu(nLineIndentation) : 0), (bForceZeroIndent || ( nLineIndentation != 0))), |
3611 | 0 | XML_algn, GetAlignment( nAlignment ), |
3612 | 0 | XML_defTabSz, sax_fastparser::UseIf(OString::number(oox::drawingml::convertHmmToEmu(nParaDefaultTabSize)), nParaDefaultTabSize > 0), |
3613 | 0 | XML_rtl, sax_fastparser::UseIf(ToPsz10(bRtl), bRtl)); |
3614 | | |
3615 | |
|
3616 | 0 | if( bHasLinespacing ) |
3617 | 0 | { |
3618 | 0 | mpFS->startElementNS(XML_a, XML_lnSpc); |
3619 | 0 | WriteLinespacing(aLineSpacing, fFirstCharHeight); |
3620 | 0 | mpFS->endElementNS( XML_a, XML_lnSpc ); |
3621 | 0 | } |
3622 | |
|
3623 | 0 | if( nParaTopMargin != 0 ) |
3624 | 0 | { |
3625 | 0 | mpFS->startElementNS(XML_a, XML_spcBef); |
3626 | 0 | { |
3627 | 0 | mpFS->singleElementNS( XML_a, XML_spcPts, |
3628 | 0 | XML_val, OString::number(toTextSpacingPoint(nParaTopMargin))); |
3629 | 0 | } |
3630 | 0 | mpFS->endElementNS( XML_a, XML_spcBef ); |
3631 | 0 | } |
3632 | |
|
3633 | 0 | if( nParaBottomMargin != 0 ) |
3634 | 0 | { |
3635 | 0 | mpFS->startElementNS(XML_a, XML_spcAft); |
3636 | 0 | { |
3637 | 0 | mpFS->singleElementNS( XML_a, XML_spcPts, |
3638 | 0 | XML_val, OString::number(toTextSpacingPoint(nParaBottomMargin))); |
3639 | 0 | } |
3640 | 0 | mpFS->endElementNS( XML_a, XML_spcAft ); |
3641 | 0 | } |
3642 | |
|
3643 | 0 | if (!bWriteNumbering) |
3644 | 0 | mpFS->singleElementNS(XML_a, XML_buNone); |
3645 | 0 | else |
3646 | 0 | WriteParagraphNumbering( rXPropSet, fFirstCharHeight, nLevel ); |
3647 | |
|
3648 | 0 | WriteParagraphTabStops( rXPropSet ); |
3649 | | |
3650 | | // do not end element for lstStyles since, defRPr should be stacked inside it |
3651 | 0 | if( nElement != XML_lvl1pPr ) |
3652 | 0 | mpFS->endElementNS( XML_a, nElement ); |
3653 | |
|
3654 | 0 | return true; |
3655 | 0 | } |
3656 | | |
3657 | | void DrawingML::WriteLstStyles(const css::uno::Reference<css::text::XTextContent>& rParagraph, |
3658 | | bool& rbOverridingCharHeight, sal_Int32& rnCharHeight, |
3659 | | const css::uno::Reference<css::beans::XPropertySet>& rXShapePropSet) |
3660 | 0 | { |
3661 | 0 | Reference<XEnumerationAccess> xAccess(rParagraph, UNO_QUERY); |
3662 | 0 | if (!xAccess.is()) |
3663 | 0 | return; |
3664 | | |
3665 | 0 | Reference<XEnumeration> xEnumeration(xAccess->createEnumeration()); |
3666 | 0 | if (!xEnumeration.is()) |
3667 | 0 | return; |
3668 | | |
3669 | | |
3670 | 0 | Reference<XTextRange> rRun; |
3671 | |
|
3672 | 0 | if (!xEnumeration->hasMoreElements()) |
3673 | 0 | return; |
3674 | | |
3675 | 0 | Any aAny(xEnumeration->nextElement()); |
3676 | 0 | if (aAny >>= rRun) |
3677 | 0 | { |
3678 | 0 | float fFirstCharHeight = rnCharHeight / 1000.; |
3679 | 0 | Reference<XPropertySet> xFirstRunPropSet(rRun, UNO_QUERY); |
3680 | 0 | Reference<XPropertySetInfo> xFirstRunPropSetInfo |
3681 | 0 | = xFirstRunPropSet->getPropertySetInfo(); |
3682 | |
|
3683 | 0 | if (xFirstRunPropSetInfo->hasPropertyByName(u"CharHeight"_ustr)) |
3684 | 0 | fFirstCharHeight = xFirstRunPropSet->getPropertyValue(u"CharHeight"_ustr).get<float>(); |
3685 | |
|
3686 | 0 | mpFS->startElementNS(XML_a, XML_lstStyle); |
3687 | 0 | if( !WriteParagraphProperties(rParagraph, fFirstCharHeight, XML_lvl1pPr) ) |
3688 | 0 | mpFS->startElementNS(XML_a, XML_lvl1pPr); |
3689 | 0 | WriteRunProperties(xFirstRunPropSet, false, XML_defRPr, true, rbOverridingCharHeight, |
3690 | 0 | rnCharHeight, GetScriptType(rRun->getString()), rXShapePropSet); |
3691 | 0 | mpFS->endElementNS(XML_a, XML_lvl1pPr); |
3692 | 0 | mpFS->endElementNS(XML_a, XML_lstStyle); |
3693 | 0 | } |
3694 | 0 | } |
3695 | | |
3696 | | void DrawingML::WriteParagraph( const Reference< XTextContent >& rParagraph, |
3697 | | bool& rbOverridingCharHeight, sal_Int32& rnCharHeight, |
3698 | | const css::uno::Reference< css::beans::XPropertySet >& rXShapePropSet) |
3699 | 0 | { |
3700 | 0 | Reference< XEnumerationAccess > access( rParagraph, UNO_QUERY ); |
3701 | 0 | if( !access.is() ) |
3702 | 0 | return; |
3703 | | |
3704 | 0 | Reference< XEnumeration > enumeration( access->createEnumeration() ); |
3705 | 0 | if( !enumeration.is() ) |
3706 | 0 | return; |
3707 | | |
3708 | 0 | mpFS->startElementNS(XML_a, XML_p); |
3709 | |
|
3710 | 0 | bool bPropertiesWritten = false; |
3711 | 0 | while( enumeration->hasMoreElements() ) |
3712 | 0 | { |
3713 | 0 | Reference< XTextRange > run; |
3714 | 0 | Any any ( enumeration->nextElement() ); |
3715 | |
|
3716 | 0 | if (any >>= run) |
3717 | 0 | { |
3718 | 0 | if( !bPropertiesWritten ) |
3719 | 0 | { |
3720 | 0 | float fFirstCharHeight = rnCharHeight / 1000.; |
3721 | 0 | Reference< XPropertySet > xFirstRunPropSet (run, UNO_QUERY); |
3722 | 0 | Reference< XPropertySetInfo > xFirstRunPropSetInfo = xFirstRunPropSet->getPropertySetInfo(); |
3723 | 0 | if( xFirstRunPropSetInfo->hasPropertyByName(u"CharHeight"_ustr) ) |
3724 | 0 | { |
3725 | 0 | fFirstCharHeight = xFirstRunPropSet->getPropertyValue(u"CharHeight"_ustr).get<float>(); |
3726 | 0 | rnCharHeight = 100 * fFirstCharHeight; |
3727 | 0 | rbOverridingCharHeight = true; |
3728 | 0 | } |
3729 | 0 | WriteParagraphProperties(rParagraph, fFirstCharHeight, XML_pPr); |
3730 | 0 | bPropertiesWritten = true; |
3731 | 0 | } |
3732 | 0 | WriteRun( run, rbOverridingCharHeight, rnCharHeight, rXShapePropSet); |
3733 | 0 | } |
3734 | 0 | } |
3735 | 0 | Reference< XPropertySet > rXPropSet( rParagraph, UNO_QUERY ); |
3736 | 0 | sal_Int16 nDummy = -1; |
3737 | 0 | WriteRunProperties(rXPropSet, false, XML_endParaRPr, false, rbOverridingCharHeight, |
3738 | 0 | rnCharHeight, nDummy, rXShapePropSet); |
3739 | |
|
3740 | 0 | mpFS->endElementNS( XML_a, XML_p ); |
3741 | 0 | } |
3742 | | |
3743 | | bool DrawingML::IsFontworkShape(const css::uno::Reference<css::beans::XPropertySet>& rXShapePropSet) |
3744 | 0 | { |
3745 | 0 | bool bResult(false); |
3746 | 0 | if (rXShapePropSet.is()) |
3747 | 0 | { |
3748 | 0 | Sequence<PropertyValue> aCustomShapeGeometryProps; |
3749 | 0 | if (GetProperty(rXShapePropSet, u"CustomShapeGeometry"_ustr)) |
3750 | 0 | { |
3751 | 0 | mAny >>= aCustomShapeGeometryProps; |
3752 | 0 | uno::Sequence<beans::PropertyValue> aTextPathSeq; |
3753 | 0 | for (const auto& rProp : aCustomShapeGeometryProps) |
3754 | 0 | { |
3755 | 0 | if (rProp.Name == "TextPath") |
3756 | 0 | { |
3757 | 0 | rProp.Value >>= aTextPathSeq; |
3758 | 0 | for (const auto& rTextPathItem : aTextPathSeq) |
3759 | 0 | { |
3760 | 0 | if (rTextPathItem.Name == "TextPath") |
3761 | 0 | { |
3762 | 0 | rTextPathItem.Value >>= bResult; |
3763 | 0 | break; |
3764 | 0 | } |
3765 | 0 | } |
3766 | 0 | break; |
3767 | 0 | } |
3768 | 0 | } |
3769 | 0 | } |
3770 | 0 | } |
3771 | 0 | return bResult; |
3772 | 0 | } |
3773 | | |
3774 | | void DrawingML::WriteText(const Reference<XInterface>& rXIface, bool bBodyPr, bool bText, |
3775 | | sal_Int32 nXmlNamespace, bool bWritePropertiesAsLstStyles) |
3776 | 0 | { |
3777 | | // ToDo: Fontwork in DOCX |
3778 | 0 | uno::Reference<XText> xXText(rXIface, UNO_QUERY); |
3779 | 0 | if( !xXText.is() ) |
3780 | 0 | return; |
3781 | | |
3782 | 0 | uno::Reference<drawing::XShape> xShape(rXIface, UNO_QUERY); |
3783 | 0 | uno::Reference<XPropertySet> rXPropSet(rXIface, UNO_QUERY); |
3784 | |
|
3785 | 0 | constexpr const sal_Int32 constDefaultLeftRightInset = 254; |
3786 | 0 | constexpr const sal_Int32 constDefaultTopBottomInset = 127; |
3787 | 0 | sal_Int32 nLeft = constDefaultLeftRightInset; |
3788 | 0 | sal_Int32 nRight = constDefaultLeftRightInset; |
3789 | 0 | sal_Int32 nTop = constDefaultTopBottomInset; |
3790 | 0 | sal_Int32 nBottom = constDefaultTopBottomInset; |
3791 | | |
3792 | | // top inset looks a bit different compared to ppt export |
3793 | | // check if something related doesn't work as expected |
3794 | 0 | if (GetProperty(rXPropSet, u"TextLeftDistance"_ustr)) |
3795 | 0 | mAny >>= nLeft; |
3796 | 0 | if (GetProperty(rXPropSet, u"TextRightDistance"_ustr)) |
3797 | 0 | mAny >>= nRight; |
3798 | 0 | if (GetProperty(rXPropSet, u"TextUpperDistance"_ustr)) |
3799 | 0 | mAny >>= nTop; |
3800 | 0 | if (GetProperty(rXPropSet, u"TextLowerDistance"_ustr)) |
3801 | 0 | mAny >>= nBottom; |
3802 | | |
3803 | | // Transform the text distance values so they are compatible with OOXML insets |
3804 | 0 | if (xShape.is()) |
3805 | 0 | { |
3806 | 0 | sal_Int32 nTextHeight = xShape->getSize().Height; // Hmm, default |
3807 | | |
3808 | | // CustomShape can have text area different from shape rectangle |
3809 | 0 | auto* pCustomShape |
3810 | 0 | = dynamic_cast<SdrObjCustomShape*>(SdrObject::getSdrObjectFromXShape(xShape)); |
3811 | 0 | if (pCustomShape) |
3812 | 0 | { |
3813 | 0 | const EnhancedCustomShape2d aCustomShape2d(*pCustomShape); |
3814 | 0 | nTextHeight = aCustomShape2d.GetTextRect().getOpenHeight(); |
3815 | 0 | if (DOCUMENT_DOCX == meDocumentType) |
3816 | 0 | nTextHeight = convertTwipToMm100(nTextHeight); |
3817 | 0 | } |
3818 | |
|
3819 | 0 | if (nTop + nBottom >= nTextHeight) |
3820 | 0 | { |
3821 | | // Effective bottom would be above effective top of text area. LO normalizes the |
3822 | | // effective text area in such case implicitly for rendering. MS needs indents so that |
3823 | | // the result is the normalized effective text area. |
3824 | 0 | std::swap(nTop, nBottom); |
3825 | 0 | nTop = nTextHeight - nTop; |
3826 | 0 | nBottom = nTextHeight - nBottom; |
3827 | 0 | } |
3828 | 0 | } |
3829 | |
|
3830 | 0 | std::optional<OString> sWritingMode; |
3831 | 0 | if (GetProperty(rXPropSet, u"TextWritingMode"_ustr)) |
3832 | 0 | { |
3833 | 0 | WritingMode eMode; |
3834 | 0 | if( ( mAny >>= eMode ) && eMode == WritingMode_TB_RL ) |
3835 | 0 | sWritingMode = "eaVert"; |
3836 | 0 | } |
3837 | 0 | if (GetProperty(rXPropSet, u"WritingMode"_ustr)) |
3838 | 0 | { |
3839 | 0 | sal_Int16 nWritingMode; |
3840 | 0 | if (mAny >>= nWritingMode) |
3841 | 0 | { |
3842 | 0 | if (nWritingMode == text::WritingMode2::TB_RL) |
3843 | 0 | sWritingMode = "eaVert"; |
3844 | 0 | else if (nWritingMode == text::WritingMode2::BT_LR) |
3845 | 0 | sWritingMode = "vert270"; |
3846 | 0 | else if (nWritingMode == text::WritingMode2::TB_RL90) |
3847 | 0 | sWritingMode = "vert"; |
3848 | 0 | else if (nWritingMode == text::WritingMode2::TB_LR) |
3849 | 0 | sWritingMode = "mongolianVert"; |
3850 | 0 | else if (nWritingMode == text::WritingMode2::STACKED) |
3851 | 0 | sWritingMode = "wordArtVert"; |
3852 | 0 | } |
3853 | 0 | } |
3854 | | |
3855 | | // read values from CustomShapeGeometry |
3856 | 0 | Sequence<drawing::EnhancedCustomShapeAdjustmentValue> aAdjustmentSeq; |
3857 | 0 | uno::Sequence<beans::PropertyValue> aTextPathSeq; |
3858 | 0 | bool bScaleX(false); |
3859 | 0 | OUString sShapeType(u"non-primitive"_ustr); |
3860 | 0 | OUString sMSWordPresetTextWarp; |
3861 | 0 | sal_Int32 nTextPreRotateAngle = 0; // degree |
3862 | 0 | std::optional<Degree100> nTextRotateAngleDeg100; // text area rotation |
3863 | |
|
3864 | 0 | if (GetProperty(rXPropSet, u"CustomShapeGeometry"_ustr)) |
3865 | 0 | { |
3866 | 0 | Sequence< PropertyValue > aProps; |
3867 | 0 | if ( mAny >>= aProps ) |
3868 | 0 | { |
3869 | 0 | for (const auto& rProp : aProps) |
3870 | 0 | { |
3871 | 0 | if (rProp.Name == "TextPreRotateAngle") |
3872 | 0 | rProp.Value >>= nTextPreRotateAngle; |
3873 | 0 | else if (rProp.Name == "AdjustmentValues") |
3874 | 0 | rProp.Value >>= aAdjustmentSeq; |
3875 | 0 | else if (rProp.Name == "TextRotateAngle") |
3876 | 0 | { |
3877 | 0 | double fTextRotateAngle = 0; // degree |
3878 | 0 | rProp.Value >>= fTextRotateAngle; |
3879 | 0 | nTextRotateAngleDeg100 = Degree100(std::lround(fTextRotateAngle * 100.0)); |
3880 | 0 | } |
3881 | 0 | else if (rProp.Name == "Type") |
3882 | 0 | rProp.Value >>= sShapeType; |
3883 | 0 | else if (rProp.Name == "TextPath") |
3884 | 0 | { |
3885 | 0 | rProp.Value >>= aTextPathSeq; |
3886 | 0 | for (const auto& rTextPathItem : aTextPathSeq) |
3887 | 0 | { |
3888 | 0 | if (rTextPathItem.Name == "ScaleX") |
3889 | 0 | rTextPathItem.Value >>= bScaleX; |
3890 | 0 | } |
3891 | 0 | } |
3892 | 0 | else if (rProp.Name == "PresetTextWarp") |
3893 | 0 | rProp.Value >>= sMSWordPresetTextWarp; |
3894 | 0 | } |
3895 | 0 | } |
3896 | 0 | } |
3897 | 0 | else |
3898 | 0 | { |
3899 | 0 | if (mpTextExport) |
3900 | 0 | { |
3901 | 0 | if (xShape) |
3902 | 0 | { |
3903 | 0 | auto xTextFrame = mpTextExport->GetUnoTextFrame(xShape); |
3904 | 0 | if (xTextFrame) |
3905 | 0 | { |
3906 | 0 | uno::Reference<beans::XPropertySet> xPropSet(xTextFrame, uno::UNO_QUERY); |
3907 | 0 | auto aAny = xPropSet->getPropertyValue(u"WritingMode"_ustr); |
3908 | 0 | sal_Int16 nWritingMode; |
3909 | 0 | if (aAny >>= nWritingMode) |
3910 | 0 | { |
3911 | 0 | switch (nWritingMode) |
3912 | 0 | { |
3913 | 0 | case WritingMode2::TB_RL: |
3914 | 0 | sWritingMode = "eaVert"; |
3915 | 0 | break; |
3916 | 0 | case WritingMode2::BT_LR: |
3917 | 0 | sWritingMode = "vert270"; |
3918 | 0 | break; |
3919 | 0 | case WritingMode2::TB_RL90: |
3920 | 0 | sWritingMode = "vert"; |
3921 | 0 | break; |
3922 | 0 | case WritingMode2::TB_LR: |
3923 | 0 | sWritingMode = "mongolianVert"; |
3924 | 0 | break; |
3925 | 0 | default: |
3926 | 0 | break; |
3927 | 0 | } |
3928 | 0 | } |
3929 | 0 | } |
3930 | 0 | } |
3931 | 0 | } |
3932 | 0 | } |
3933 | | |
3934 | | // read InteropGrabBag if any |
3935 | 0 | std::optional<OUString> sHorzOverflow; |
3936 | 0 | std::optional<OUString> sVertOverflow; |
3937 | 0 | bool bUpright = false; |
3938 | 0 | std::optional<OString> isUpright; |
3939 | 0 | if (rXPropSet->getPropertySetInfo()->hasPropertyByName(u"InteropGrabBag"_ustr)) |
3940 | 0 | { |
3941 | 0 | uno::Sequence<beans::PropertyValue> aGrabBag; |
3942 | 0 | rXPropSet->getPropertyValue(u"InteropGrabBag"_ustr) >>= aGrabBag; |
3943 | 0 | for (const auto& aProp : aGrabBag) |
3944 | 0 | { |
3945 | 0 | if (aProp.Name == "Upright") |
3946 | 0 | { |
3947 | 0 | aProp.Value >>= bUpright; |
3948 | 0 | isUpright = OString(bUpright ? "1" : "0"); |
3949 | 0 | } |
3950 | 0 | else if (aProp.Name == "horzOverflow") |
3951 | 0 | { |
3952 | 0 | OUString sValue; |
3953 | 0 | aProp.Value >>= sValue; |
3954 | 0 | sHorzOverflow = sValue; |
3955 | 0 | } |
3956 | 0 | else if (aProp.Name == "vertOverflow") |
3957 | 0 | { |
3958 | 0 | OUString sValue; |
3959 | 0 | aProp.Value >>= sValue; |
3960 | 0 | sVertOverflow = sValue; |
3961 | 0 | } |
3962 | 0 | } |
3963 | 0 | } |
3964 | |
|
3965 | 0 | bool bIsFontworkShape(IsFontworkShape(rXPropSet)); |
3966 | 0 | OUString sPresetWarp(PresetGeometryTypeNames::GetMsoName(sShapeType)); |
3967 | | // ODF may have user defined TextPath, use "textPlain" as ersatz. |
3968 | 0 | if (sPresetWarp.isEmpty()) |
3969 | 0 | sPresetWarp = bIsFontworkShape ? std::u16string_view(u"textPlain") : std::u16string_view(u"textNoShape"); |
3970 | |
|
3971 | 0 | bool bFromWordArt = !bScaleX |
3972 | 0 | && ( sPresetWarp == "textArchDown" || sPresetWarp == "textArchUp" |
3973 | 0 | || sPresetWarp == "textButton" || sPresetWarp == "textCircle"); |
3974 | | |
3975 | | // Fontwork shapes in LO ignore insets in rendering, Word interprets them. |
3976 | 0 | if (GetDocumentType() == DOCUMENT_DOCX && bIsFontworkShape) |
3977 | 0 | { |
3978 | 0 | nLeft = 0; |
3979 | 0 | nRight = 0; |
3980 | 0 | nTop = 0; |
3981 | 0 | nBottom = 0; |
3982 | 0 | } |
3983 | |
|
3984 | 0 | if (bUpright) |
3985 | 0 | { |
3986 | 0 | Degree100 nShapeRotateAngleDeg100(0_deg100); |
3987 | 0 | if (GetProperty(rXPropSet, u"RotateAngle"_ustr)) |
3988 | 0 | nShapeRotateAngleDeg100 = Degree100(mAny.get<sal_Int32>()); |
3989 | | // Depending on shape rotation, the import has made 90deg changes to properties |
3990 | | // "TextPreRotateAngle" and "TextRotateAngle". Revert it. |
3991 | 0 | bool bWasAngleChanged |
3992 | 0 | = (nShapeRotateAngleDeg100 > 4500_deg100 && nShapeRotateAngleDeg100 <= 13500_deg100) |
3993 | 0 | || (nShapeRotateAngleDeg100 > 22500_deg100 |
3994 | 0 | && nShapeRotateAngleDeg100 <= 31500_deg100); |
3995 | 0 | if (bWasAngleChanged) |
3996 | 0 | { |
3997 | 0 | nTextRotateAngleDeg100 = nTextRotateAngleDeg100.value_or(0_deg100) + 9000_deg100; |
3998 | 0 | nTextPreRotateAngle -= 90; |
3999 | 0 | } |
4000 | | // If text is no longer upright, user has changed something. Do not write 'upright' then. |
4001 | | // This try to detect the case assumes, that the text area rotation was 0 in the original |
4002 | | // MS Office document. That is likely because MS Office has no UI to set it and the |
4003 | | // predefined SmartArt shapes, which use it, do not use 'upright'. |
4004 | 0 | Degree100 nAngleSum = nShapeRotateAngleDeg100 + nTextRotateAngleDeg100.value_or(0_deg100); |
4005 | 0 | if (abs(NormAngle18000(nAngleSum)) < 100_deg100) // consider inaccuracy from rounding |
4006 | 0 | { |
4007 | 0 | nTextRotateAngleDeg100.reset(); // 'upright' does not overrule text area rotation. |
4008 | 0 | } |
4009 | 0 | else |
4010 | 0 | { |
4011 | | // User changes. Keep current angles. |
4012 | 0 | isUpright.reset(); |
4013 | 0 | if (bWasAngleChanged) |
4014 | 0 | { |
4015 | 0 | nTextPreRotateAngle += 90; |
4016 | 0 | nTextRotateAngleDeg100 = nTextRotateAngleDeg100.value_or(0_deg100) - 9000_deg100; |
4017 | 0 | } |
4018 | 0 | } |
4019 | 0 | } |
4020 | | |
4021 | | // ToDo: Unsure about this. Need to investigate shapes from diagram import, especially diagrams |
4022 | | // with vertical text directions. |
4023 | 0 | if (nTextPreRotateAngle != 0 && !sWritingMode) |
4024 | 0 | { |
4025 | 0 | if (nTextPreRotateAngle == -90 || nTextPreRotateAngle == 270) |
4026 | 0 | sWritingMode = "vert"; |
4027 | 0 | else if (nTextPreRotateAngle == -270 || nTextPreRotateAngle == 90) |
4028 | 0 | sWritingMode = "vert270"; |
4029 | 0 | else if (nTextPreRotateAngle == -180 || nTextPreRotateAngle == 180) |
4030 | 0 | { |
4031 | | #if defined __GNUC__ && !defined __clang__ && __GNUC__ == 12 |
4032 | | #pragma GCC diagnostic push |
4033 | | #pragma GCC diagnostic ignored "-Wmaybe-uninitialized" |
4034 | | #endif |
4035 | 0 | nTextRotateAngleDeg100 |
4036 | 0 | = NormAngle18000(nTextRotateAngleDeg100.value_or(0_deg100) + 18000_deg100); |
4037 | | #if defined __GNUC__ && !defined __clang__ && __GNUC__ == 12 |
4038 | | #pragma GCC diagnostic pop |
4039 | | #endif |
4040 | | // ToDo: Examine insets. They might need rotation too. Check diagrams (SmartArt). |
4041 | 0 | } |
4042 | 0 | else |
4043 | 0 | SAL_WARN("oox", "unsuitable value for TextPreRotateAngle:" << nTextPreRotateAngle); |
4044 | 0 | } |
4045 | 0 | else if (nTextPreRotateAngle != 0 && sWritingMode && sWritingMode.value() == "eaVert") |
4046 | 0 | { |
4047 | | // ToDo: eaVert plus 270deg clockwise rotation has to be written with vert="horz" |
4048 | | // plus attribute 'normalEastAsianFlow="1"' on the <wps:wsp> element. |
4049 | 0 | } |
4050 | | // else nothing to do |
4051 | | |
4052 | | // Our WritingMode introduces text pre rotation which includes padding, MSO vert does not include |
4053 | | // padding. Therefore set padding so, that is looks the same in MSO as in LO. |
4054 | 0 | if (sWritingMode) |
4055 | 0 | { |
4056 | 0 | if (sWritingMode.value() == "vert" || sWritingMode.value() == "eaVert") |
4057 | 0 | { |
4058 | 0 | sal_Int32 nHelp = nLeft; |
4059 | 0 | nLeft = nBottom; |
4060 | 0 | nBottom = nRight; |
4061 | 0 | nRight = nTop; |
4062 | 0 | nTop = nHelp; |
4063 | 0 | } |
4064 | 0 | else if (sWritingMode.value() == "vert270") |
4065 | 0 | { |
4066 | 0 | sal_Int32 nHelp = nLeft; |
4067 | 0 | nLeft = nTop; |
4068 | 0 | nTop = nRight; |
4069 | 0 | nRight = nBottom; |
4070 | 0 | nBottom = nHelp; |
4071 | 0 | } |
4072 | 0 | else if (sWritingMode.value() == "mongolianVert") |
4073 | 0 | { |
4074 | | // ToDo: Examine padding |
4075 | 0 | } |
4076 | 0 | } |
4077 | | |
4078 | |
|
4079 | 0 | std::optional<OString> sTextRotateAngleMSUnit; |
4080 | 0 | if (nTextRotateAngleDeg100.has_value()) |
4081 | | #if defined __GNUC__ && !defined __clang__ && __GNUC__ == 12 |
4082 | | #pragma GCC diagnostic push |
4083 | | #pragma GCC diagnostic ignored "-Wmaybe-uninitialized" |
4084 | | #endif |
4085 | 0 | sTextRotateAngleMSUnit |
4086 | 0 | = oox::drawingml::calcRotationValue(nTextRotateAngleDeg100.value().get()); |
4087 | | #if defined __GNUC__ && !defined __clang__ && __GNUC__ == 12 |
4088 | | #pragma GCC diagnostic pop |
4089 | | #endif |
4090 | | |
4091 | | // Prepare attributes 'anchor' and 'anchorCtr' |
4092 | | // LibreOffice has 12 value sets, MS Office only 6. We map them so, that it reverses the |
4093 | | // 6 mappings from import, and we assign the others approximately. |
4094 | 0 | TextVerticalAdjust eVerticalAlignment(TextVerticalAdjust_TOP); |
4095 | 0 | if (GetProperty(rXPropSet, u"TextVerticalAdjust"_ustr)) |
4096 | 0 | mAny >>= eVerticalAlignment; |
4097 | 0 | TextHorizontalAdjust eHorizontalAlignment(TextHorizontalAdjust_CENTER); |
4098 | 0 | if (GetProperty(rXPropSet, u"TextHorizontalAdjust"_ustr)) |
4099 | 0 | mAny >>= eHorizontalAlignment; |
4100 | |
|
4101 | 0 | const char* sAnchor = nullptr; |
4102 | 0 | bool bAnchorCtr = false; |
4103 | 0 | if (sWritingMode.has_value() |
4104 | 0 | && (sWritingMode.value() == "eaVert" || sWritingMode.value() == "mongolianVert")) |
4105 | 0 | { |
4106 | 0 | bAnchorCtr = eVerticalAlignment == TextVerticalAdjust_CENTER |
4107 | 0 | || eVerticalAlignment == TextVerticalAdjust_BOTTOM |
4108 | 0 | || eVerticalAlignment == TextVerticalAdjust_BLOCK; |
4109 | 0 | switch (eHorizontalAlignment) |
4110 | 0 | { |
4111 | 0 | case TextHorizontalAdjust_CENTER: |
4112 | 0 | sAnchor = "ctr"; |
4113 | 0 | break; |
4114 | 0 | case TextHorizontalAdjust_LEFT: |
4115 | 0 | sAnchor = sWritingMode.value() == "eaVert" ? "b" : "t"; |
4116 | 0 | break; |
4117 | 0 | case TextHorizontalAdjust_RIGHT: |
4118 | 0 | default: // TextHorizontalAdjust_BLOCK, should not happen |
4119 | 0 | sAnchor = sWritingMode.value() == "eaVert" ? "t" : "b"; |
4120 | 0 | break; |
4121 | 0 | } |
4122 | 0 | } |
4123 | 0 | else |
4124 | 0 | { |
4125 | 0 | bAnchorCtr = eHorizontalAlignment == TextHorizontalAdjust_CENTER |
4126 | 0 | || eHorizontalAlignment == TextHorizontalAdjust_RIGHT; |
4127 | 0 | sAnchor = GetTextVerticalAdjust(eVerticalAlignment); |
4128 | 0 | } |
4129 | | |
4130 | 0 | bool bHasWrap = false; |
4131 | 0 | bool bWrap = false; |
4132 | | // Only custom shapes obey the TextWordWrap option, normal text always wraps. |
4133 | 0 | if (dynamic_cast<SvxCustomShape*>(rXIface.get()) && GetProperty(rXPropSet, u"TextWordWrap"_ustr)) |
4134 | 0 | { |
4135 | 0 | mAny >>= bWrap; |
4136 | 0 | bHasWrap = true; |
4137 | 0 | } |
4138 | | |
4139 | | // tdf#134401: If AUTOGROWWIDTH and AUTOGROWHEIGHT are set, then export it as TextWordWrap |
4140 | 0 | if (SvxShapeText* pShpTxt = dynamic_cast<SvxShapeText*>(rXIface.get())) |
4141 | 0 | { |
4142 | 0 | const sdr::properties::BaseProperties& rProperties |
4143 | 0 | = pShpTxt->GetSdrObject()->GetProperties(); |
4144 | |
|
4145 | 0 | const SdrOnOffItem& rSdrTextFitWidth = rProperties.GetItem(SDRATTR_TEXT_AUTOGROWWIDTH); |
4146 | 0 | const SdrOnOffItem& rSdrTextFitHeight = rProperties.GetItem(SDRATTR_TEXT_AUTOGROWHEIGHT); |
4147 | |
|
4148 | 0 | if (rSdrTextFitWidth.GetValue() == true && rSdrTextFitHeight.GetValue() == true) |
4149 | 0 | { |
4150 | 0 | bHasWrap = true; |
4151 | 0 | bWrap = false; |
4152 | 0 | } |
4153 | 0 | } |
4154 | |
|
4155 | 0 | if (bBodyPr) |
4156 | 0 | { |
4157 | 0 | const char* pWrap = (bHasWrap && !bWrap) || bIsFontworkShape ? "none" : nullptr; |
4158 | 0 | if (GetDocumentType() == DOCUMENT_DOCX) |
4159 | 0 | { |
4160 | | // In case of DOCX, if we want to have the same effect as |
4161 | | // TextShape's automatic word wrapping, then we need to set |
4162 | | // wrapping to square. |
4163 | 0 | uno::Reference<lang::XServiceInfo> xServiceInfo(rXIface, uno::UNO_QUERY); |
4164 | 0 | if ((xServiceInfo.is() && xServiceInfo->supportsService(u"com.sun.star.drawing.TextShape"_ustr)) |
4165 | 0 | || bIsFontworkShape) |
4166 | 0 | pWrap = "square"; |
4167 | 0 | } |
4168 | |
|
4169 | 0 | sal_Int16 nCols = 0; |
4170 | 0 | sal_Int32 nColSpacing = -1; |
4171 | 0 | if (GetProperty(rXPropSet, u"TextColumns"_ustr)) |
4172 | 0 | { |
4173 | 0 | if (css::uno::Reference<css::text::XTextColumns> xCols{ mAny, css::uno::UNO_QUERY }) |
4174 | 0 | { |
4175 | 0 | nCols = xCols->getColumnCount(); |
4176 | 0 | if (css::uno::Reference<css::beans::XPropertySet> xProps{ mAny, |
4177 | 0 | css::uno::UNO_QUERY }) |
4178 | 0 | { |
4179 | 0 | if (GetProperty(xProps, u"AutomaticDistance"_ustr)) |
4180 | 0 | mAny >>= nColSpacing; |
4181 | 0 | } |
4182 | 0 | } |
4183 | 0 | } |
4184 | |
|
4185 | 0 | if (!sVertOverflow && GetProperty(rXPropSet, u"TextClipVerticalOverflow"_ustr) && mAny.get<bool>()) |
4186 | 0 | { |
4187 | 0 | sVertOverflow = "clip"; |
4188 | 0 | } |
4189 | | |
4190 | | // tdf#151134 When writing placeholder shapes, inset must be explicitly specified |
4191 | 0 | bool bRequireInset = GetProperty(rXPropSet, u"IsPresentationObject"_ustr) && rXPropSet->getPropertyValue(u"IsPresentationObject"_ustr).get<bool>(); |
4192 | |
|
4193 | 0 | mpFS->startElementNS( (nXmlNamespace ? nXmlNamespace : XML_a), XML_bodyPr, |
4194 | 0 | XML_numCol, sax_fastparser::UseIf(OString::number(nCols), nCols > 0), |
4195 | 0 | XML_spcCol, sax_fastparser::UseIf(OString::number(oox::drawingml::convertHmmToEmu(nColSpacing)), nCols > 0 && nColSpacing >= 0), |
4196 | 0 | XML_wrap, pWrap, |
4197 | 0 | XML_horzOverflow, sHorzOverflow, |
4198 | 0 | XML_vertOverflow, sVertOverflow, |
4199 | 0 | XML_fromWordArt, sax_fastparser::UseIf("1", bFromWordArt), |
4200 | 0 | XML_lIns, sax_fastparser::UseIf(OString::number(oox::drawingml::convertHmmToEmu(nLeft)), |
4201 | 0 | bRequireInset || nLeft != constDefaultLeftRightInset), |
4202 | 0 | XML_rIns, sax_fastparser::UseIf(OString::number(oox::drawingml::convertHmmToEmu(nRight)), |
4203 | 0 | bRequireInset || nRight != constDefaultLeftRightInset), |
4204 | 0 | XML_tIns, sax_fastparser::UseIf(OString::number(oox::drawingml::convertHmmToEmu(nTop)), |
4205 | 0 | bRequireInset || nTop != constDefaultTopBottomInset), |
4206 | 0 | XML_bIns, sax_fastparser::UseIf(OString::number(oox::drawingml::convertHmmToEmu(nBottom)), |
4207 | 0 | bRequireInset || nBottom != constDefaultTopBottomInset), |
4208 | 0 | XML_anchor, sAnchor, |
4209 | 0 | XML_anchorCtr, sax_fastparser::UseIf("1", bAnchorCtr), |
4210 | 0 | XML_vert, sWritingMode, |
4211 | 0 | XML_upright, isUpright, |
4212 | 0 | XML_rot, sTextRotateAngleMSUnit); |
4213 | |
|
4214 | 0 | if (bIsFontworkShape) |
4215 | 0 | { |
4216 | 0 | if (aAdjustmentSeq.hasElements()) |
4217 | 0 | { |
4218 | 0 | mpFS->startElementNS(XML_a, XML_prstTxWarp, XML_prst, sPresetWarp); |
4219 | 0 | mpFS->startElementNS(XML_a, XML_avLst); |
4220 | 0 | bool bHasTwoHandles( |
4221 | 0 | sPresetWarp == "textArchDownPour" || sPresetWarp == "textArchUpPour" |
4222 | 0 | || sPresetWarp == "textButtonPour" || sPresetWarp == "textCirclePour" |
4223 | 0 | || sPresetWarp == "textDoubleWave1" || sPresetWarp == "textWave1" |
4224 | 0 | || sPresetWarp == "textWave2" || sPresetWarp == "textWave4"); |
4225 | 0 | for (sal_Int32 i = 0, nElems = aAdjustmentSeq.getLength(); i < nElems; ++i ) |
4226 | 0 | { |
4227 | 0 | OString sName = "adj" + (bHasTwoHandles ? OString::number(i + 1) : OString()); |
4228 | 0 | double fValue(0.0); |
4229 | 0 | if (aAdjustmentSeq[i].Value.getValueTypeClass() == TypeClass_DOUBLE) |
4230 | 0 | aAdjustmentSeq[i].Value >>= fValue; |
4231 | 0 | else |
4232 | 0 | { |
4233 | 0 | sal_Int32 nNumber(0); |
4234 | 0 | aAdjustmentSeq[i].Value >>= nNumber; |
4235 | 0 | fValue = static_cast<double>(nNumber); |
4236 | 0 | } |
4237 | | // Convert from binary coordinate system with viewBox "0 0 21600 21600" and simple degree |
4238 | | // to DrawingML with coordinate range 0..100000 and angle in 1/60000 degree. |
4239 | | // Reverse to conversion in lcl_createPresetShape in drawingml/shape.cxx on import. |
4240 | 0 | if (sPresetWarp == "textArchDown" || sPresetWarp == "textArchUp" |
4241 | 0 | || sPresetWarp == "textButton" || sPresetWarp == "textCircle" |
4242 | 0 | || ((i == 0) |
4243 | 0 | && (sPresetWarp == "textArchDownPour" || sPresetWarp == "textArchUpPour" |
4244 | 0 | || sPresetWarp == "textButtonPour" || sPresetWarp == "textCirclePour"))) |
4245 | 0 | { |
4246 | 0 | fValue *= 60000.0; |
4247 | 0 | if (fValue < 0) |
4248 | 0 | fValue += 21600000; |
4249 | 0 | } |
4250 | 0 | else if ((i == 1) |
4251 | 0 | && (sPresetWarp == "textDoubleWave1" || sPresetWarp == "textWave1" |
4252 | 0 | || sPresetWarp == "textWave2" || sPresetWarp == "textWave4")) |
4253 | 0 | { |
4254 | 0 | fValue = fValue / 0.216 - 50000.0; |
4255 | 0 | } |
4256 | 0 | else if ((i == 1) |
4257 | 0 | && (sPresetWarp == "textArchDownPour" |
4258 | 0 | || sPresetWarp == "textArchUpPour" |
4259 | 0 | || sPresetWarp == "textButtonPour" |
4260 | 0 | || sPresetWarp == "textCirclePour")) |
4261 | 0 | { |
4262 | 0 | fValue /= 0.108; |
4263 | 0 | } |
4264 | 0 | else |
4265 | 0 | { |
4266 | 0 | fValue /= 0.216; |
4267 | 0 | } |
4268 | 0 | OString sFmla = "val " + OString::number(std::lround(fValue)); |
4269 | 0 | mpFS->singleElementNS(XML_a, XML_gd, XML_name, sName, XML_fmla, sFmla); |
4270 | | // There exists faulty Favorite shapes with one handle but two adjustment values. |
4271 | 0 | if (!bHasTwoHandles) |
4272 | 0 | break; |
4273 | 0 | } |
4274 | 0 | mpFS->endElementNS(XML_a, XML_avLst); |
4275 | 0 | mpFS->endElementNS(XML_a, XML_prstTxWarp); |
4276 | 0 | } |
4277 | 0 | else |
4278 | 0 | { |
4279 | 0 | mpFS->singleElementNS(XML_a, XML_prstTxWarp, XML_prst, sPresetWarp); |
4280 | 0 | } |
4281 | 0 | } |
4282 | 0 | else if (GetDocumentType() == DOCUMENT_DOCX) |
4283 | 0 | { |
4284 | | // interim solution for fdo#80897, roundtrip DOCX > LO > DOCX |
4285 | 0 | if (!sMSWordPresetTextWarp.isEmpty()) |
4286 | 0 | mpFS->singleElementNS(XML_a, XML_prstTxWarp, XML_prst, sMSWordPresetTextWarp); |
4287 | 0 | } |
4288 | |
|
4289 | 0 | if (GetDocumentType() == DOCUMENT_DOCX || GetDocumentType() == DOCUMENT_XLSX) |
4290 | 0 | { |
4291 | | // tdf#112312: only custom shapes obey the TextAutoGrowHeight option |
4292 | 0 | bool bTextAutoGrowHeight = false; |
4293 | 0 | auto pSdrObjCustomShape = xShape.is() ? dynamic_cast<SdrObjCustomShape*>(SdrObject::getSdrObjectFromXShape(xShape)) : nullptr; |
4294 | 0 | if (pSdrObjCustomShape && GetProperty(rXPropSet, u"TextAutoGrowHeight"_ustr)) |
4295 | 0 | { |
4296 | 0 | mAny >>= bTextAutoGrowHeight; |
4297 | 0 | } |
4298 | 0 | mpFS->singleElementNS(XML_a, (bTextAutoGrowHeight ? XML_spAutoFit : XML_noAutofit)); |
4299 | 0 | } |
4300 | 0 | if (GetDocumentType() == DOCUMENT_PPTX) |
4301 | 0 | { |
4302 | 0 | TextFitToSizeType eFit = TextFitToSizeType_NONE; |
4303 | 0 | if (GetProperty(rXPropSet, u"TextFitToSize"_ustr)) |
4304 | 0 | mAny >>= eFit; |
4305 | |
|
4306 | 0 | if (eFit == TextFitToSizeType_AUTOFIT) |
4307 | 0 | { |
4308 | 0 | const sal_Int32 MAX_SCALE_VAL = 100000; |
4309 | 0 | sal_Int32 nFontScale = MAX_SCALE_VAL; |
4310 | 0 | sal_Int32 nSpacingReduction = 0; |
4311 | 0 | SvxShapeText* pTextShape = dynamic_cast<SvxShapeText*>(rXIface.get()); |
4312 | 0 | if (pTextShape) |
4313 | 0 | { |
4314 | 0 | SdrTextObj* pTextObject = DynCastSdrTextObj(pTextShape->GetSdrObject()); |
4315 | 0 | if (pTextObject) |
4316 | 0 | { |
4317 | 0 | nFontScale = sal_Int32(pTextObject->GetFontScale() * 100000.0); |
4318 | 0 | nSpacingReduction = sal_Int32((1.0 - pTextObject->GetSpacingScale()) * 100000.0); |
4319 | 0 | } |
4320 | 0 | } |
4321 | |
|
4322 | 0 | bool bExportFontScale = false; |
4323 | 0 | if (nFontScale < MAX_SCALE_VAL && nFontScale > 0) |
4324 | 0 | bExportFontScale = true; |
4325 | |
|
4326 | 0 | bool bExportSpaceReduction = false; |
4327 | 0 | if (nSpacingReduction < MAX_SCALE_VAL && nSpacingReduction > 0) |
4328 | 0 | bExportSpaceReduction = true; |
4329 | |
|
4330 | 0 | mpFS->singleElementNS(XML_a, XML_normAutofit, |
4331 | 0 | XML_fontScale, sax_fastparser::UseIf(OString::number(nFontScale), bExportFontScale), |
4332 | 0 | XML_lnSpcReduction, sax_fastparser::UseIf(OString::number(nSpacingReduction), bExportSpaceReduction)); |
4333 | 0 | } |
4334 | 0 | else |
4335 | 0 | { |
4336 | 0 | bool bAutoGrowHeightEnabled = false; |
4337 | 0 | const SdrObject* pObj = xShape.is() ? SdrObject::getSdrObjectFromXShape(xShape) : nullptr; |
4338 | 0 | if (pObj) |
4339 | 0 | { |
4340 | 0 | switch (pObj->GetObjIdentifier()) |
4341 | 0 | { |
4342 | 0 | case SdrObjKind::NONE: |
4343 | 0 | case SdrObjKind::Text: |
4344 | 0 | case SdrObjKind::TitleText: |
4345 | 0 | case SdrObjKind::OutlineText: |
4346 | 0 | case SdrObjKind::Caption: |
4347 | 0 | case SdrObjKind::CustomShape: |
4348 | 0 | bAutoGrowHeightEnabled = true; |
4349 | 0 | break; |
4350 | 0 | default: |
4351 | 0 | bAutoGrowHeightEnabled = false; |
4352 | 0 | } |
4353 | 0 | } |
4354 | | |
4355 | 0 | bool bTextAutoGrowHeight = false; |
4356 | 0 | if (bAutoGrowHeightEnabled && GetProperty(rXPropSet, u"TextAutoGrowHeight"_ustr)) |
4357 | 0 | mAny >>= bTextAutoGrowHeight; |
4358 | 0 | mpFS->singleElementNS(XML_a, (bTextAutoGrowHeight ? XML_spAutoFit : XML_noAutofit)); |
4359 | 0 | } |
4360 | 0 | } |
4361 | | |
4362 | 0 | Write3DEffects( rXPropSet, /*bIsText=*/true ); |
4363 | |
|
4364 | 0 | mpFS->endElementNS((nXmlNamespace ? nXmlNamespace : XML_a), XML_bodyPr); |
4365 | 0 | } |
4366 | | |
4367 | 0 | Reference< XEnumerationAccess > access( xXText, UNO_QUERY ); |
4368 | 0 | if( !access.is() || !bText ) |
4369 | 0 | return; |
4370 | | |
4371 | 0 | Reference< XEnumeration > enumeration( access->createEnumeration() ); |
4372 | 0 | if( !enumeration.is() ) |
4373 | 0 | return; |
4374 | | |
4375 | 0 | SdrObject* pSdrObject = xShape.is() ? SdrObject::getSdrObjectFromXShape(xShape) : nullptr; |
4376 | 0 | const SdrTextObj* pTxtObj = DynCastSdrTextObj( pSdrObject ); |
4377 | 0 | if (pTxtObj && mpTextExport) |
4378 | 0 | { |
4379 | 0 | std::vector<beans::PropertyValue> aOldCharFillPropVec; |
4380 | 0 | if (bIsFontworkShape) |
4381 | 0 | { |
4382 | | // Users may have set the character fill properties for more convenient editing. |
4383 | | // Save the properties before changing them for Fontwork export. |
4384 | 0 | FontworkHelpers::collectCharColorProps(xXText, aOldCharFillPropVec); |
4385 | | // Word has properties for abc-transform in the run properties of the text of the shape. |
4386 | | // Writer has the Fontwork properties as shape properties. Create the character fill |
4387 | | // properties needed for export from the shape fill properties |
4388 | | // and apply them to all runs. |
4389 | 0 | std::vector<beans::PropertyValue> aExportCharFillPropVec; |
4390 | 0 | FontworkHelpers::createCharFillPropsFromShape(rXPropSet, aExportCharFillPropVec); |
4391 | 0 | FontworkHelpers::applyPropsToRuns(aExportCharFillPropVec, xXText); |
4392 | | // Import has converted some items from CharInteropGrabBag to fill and line |
4393 | | // properties of the shape. For export we convert them back because users might have |
4394 | | // changed them. And we create them in case we come from an odt document. |
4395 | 0 | std::vector<beans::PropertyValue> aUpdatePropVec; |
4396 | 0 | FontworkHelpers::createCharInteropGrabBagUpdatesFromShapeProps(rXPropSet, aUpdatePropVec); |
4397 | 0 | FontworkHelpers::applyUpdatesToCharInteropGrabBag(aUpdatePropVec, xXText); |
4398 | 0 | } |
4399 | |
|
4400 | 0 | std::optional<OutlinerParaObject> pParaObj; |
4401 | | |
4402 | | /* |
4403 | | #i13885# |
4404 | | When the object is actively being edited, that text is not set into |
4405 | | the objects normal text object, but lives in a separate object. |
4406 | | */ |
4407 | 0 | if (pTxtObj->IsTextEditActive()) |
4408 | 0 | { |
4409 | 0 | pParaObj = pTxtObj->CreateEditOutlinerParaObject(); |
4410 | 0 | } |
4411 | 0 | else if (pTxtObj->GetOutlinerParaObject()) |
4412 | 0 | pParaObj = *pTxtObj->GetOutlinerParaObject(); |
4413 | |
|
4414 | 0 | if (pParaObj) |
4415 | 0 | { |
4416 | | // this is reached only in case some text is attached to the shape |
4417 | 0 | mpTextExport->WriteOutliner(*pParaObj); |
4418 | 0 | } |
4419 | |
|
4420 | 0 | if (bIsFontworkShape) |
4421 | 0 | FontworkHelpers::applyPropsToRuns(aOldCharFillPropVec, xXText); |
4422 | 0 | return; |
4423 | 0 | } |
4424 | | |
4425 | 0 | bool bOverridingCharHeight = false; |
4426 | 0 | sal_Int32 nCharHeight = -1; |
4427 | 0 | bool bFirstParagraph = true; |
4428 | | |
4429 | | // tdf#144092 For shapes without text: Export run properties (into |
4430 | | // endParaRPr) from the shape's propset instead of the paragraph's. |
4431 | 0 | if(xXText->getString().isEmpty() && enumeration->hasMoreElements()) |
4432 | 0 | { |
4433 | 0 | Any aAny (enumeration->nextElement()); |
4434 | 0 | Reference<XTextContent> xParagraph; |
4435 | 0 | if( aAny >>= xParagraph ) |
4436 | 0 | { |
4437 | 0 | mpFS->startElementNS(XML_a, XML_p); |
4438 | 0 | WriteParagraphProperties(xParagraph, nCharHeight, XML_pPr); |
4439 | 0 | sal_Int16 nDummy = -1; |
4440 | 0 | WriteRunProperties(rXPropSet, false, XML_endParaRPr, false, |
4441 | 0 | bOverridingCharHeight, nCharHeight, nDummy, rXPropSet); |
4442 | 0 | mpFS->endElementNS(XML_a, XML_p); |
4443 | 0 | } |
4444 | 0 | return; |
4445 | 0 | } |
4446 | | |
4447 | 0 | while( enumeration->hasMoreElements() ) |
4448 | 0 | { |
4449 | 0 | Reference< XTextContent > paragraph; |
4450 | 0 | Any any ( enumeration->nextElement() ); |
4451 | |
|
4452 | 0 | if( any >>= paragraph) |
4453 | 0 | { |
4454 | 0 | if (bFirstParagraph && bWritePropertiesAsLstStyles) |
4455 | 0 | WriteLstStyles(paragraph, bOverridingCharHeight, nCharHeight, rXPropSet); |
4456 | |
|
4457 | 0 | WriteParagraph(paragraph, bOverridingCharHeight, nCharHeight, rXPropSet); |
4458 | 0 | bFirstParagraph = false; |
4459 | 0 | } |
4460 | 0 | } |
4461 | 0 | } |
4462 | | |
4463 | | void DrawingML::WritePresetShape( const OString& pShape , std::vector< std::pair<sal_Int32,sal_Int32>> & rAvList ) |
4464 | 0 | { |
4465 | 0 | mpFS->startElementNS(XML_a, XML_prstGeom, XML_prst, pShape); |
4466 | 0 | if ( !rAvList.empty() ) |
4467 | 0 | { |
4468 | |
|
4469 | 0 | mpFS->startElementNS(XML_a, XML_avLst); |
4470 | 0 | for (auto const& elem : rAvList) |
4471 | 0 | { |
4472 | 0 | OString sName = "adj" + ( ( elem.first > 0 ) ? OString::number(elem.first) : OString() ); |
4473 | 0 | OString sFmla = "val " + OString::number( elem.second ); |
4474 | |
|
4475 | 0 | mpFS->singleElementNS(XML_a, XML_gd, XML_name, sName, XML_fmla, sFmla); |
4476 | 0 | } |
4477 | 0 | mpFS->endElementNS( XML_a, XML_avLst ); |
4478 | 0 | } |
4479 | 0 | else |
4480 | 0 | mpFS->singleElementNS(XML_a, XML_avLst); |
4481 | |
|
4482 | 0 | mpFS->endElementNS( XML_a, XML_prstGeom ); |
4483 | 0 | } |
4484 | | |
4485 | | void DrawingML::WritePresetShape( const OString& pShape ) |
4486 | 0 | { |
4487 | 0 | mpFS->startElementNS(XML_a, XML_prstGeom, XML_prst, pShape); |
4488 | 0 | mpFS->singleElementNS(XML_a, XML_avLst); |
4489 | 0 | mpFS->endElementNS( XML_a, XML_prstGeom ); |
4490 | 0 | } |
4491 | | |
4492 | | static std::map< OString, std::vector<OString> > lcl_getAdjNames() |
4493 | 0 | { |
4494 | 0 | std::map< OString, std::vector<OString> > aRet; |
4495 | |
|
4496 | 0 | OUString aPath(u"$BRAND_BASE_DIR/" LIBO_SHARE_FOLDER "/filter/oox-drawingml-adj-names"_ustr); |
4497 | 0 | rtl::Bootstrap::expandMacros(aPath); |
4498 | 0 | SvFileStream aStream(aPath, StreamMode::READ); |
4499 | 0 | if (aStream.GetError() != ERRCODE_NONE) |
4500 | 0 | SAL_WARN("oox.shape", "failed to open oox-drawingml-adj-names"); |
4501 | 0 | OStringBuffer aLine; |
4502 | 0 | while (aStream.ReadLine(aLine)) |
4503 | 0 | { |
4504 | 0 | sal_Int32 nIndex = 0; |
4505 | | // Each line is in a "key\tvalue" format: read the key, the rest is the value. |
4506 | 0 | OString aKey( o3tl::getToken(aLine, 0, '\t', nIndex) ); |
4507 | 0 | if (nIndex >= 0) |
4508 | 0 | { |
4509 | 0 | OString aValue( std::string_view(aLine).substr(nIndex) ); |
4510 | 0 | aRet[aKey].push_back(aValue); |
4511 | 0 | } |
4512 | 0 | else |
4513 | 0 | { |
4514 | 0 | SAL_WARN("oox.shape", "skipping invalid line: " << std::string_view(aLine)); |
4515 | 0 | } |
4516 | 0 | } |
4517 | 0 | return aRet; |
4518 | 0 | } |
4519 | | |
4520 | | void DrawingML::WritePresetShape( const OString& pShape, MSO_SPT eShapeType, bool bPredefinedHandlesUsed, const PropertyValue& rProp ) |
4521 | 0 | { |
4522 | 0 | static std::map< OString, std::vector<OString> > aAdjMap = lcl_getAdjNames(); |
4523 | | // If there are predefined adj names for this shape type, look them up now. |
4524 | 0 | std::vector<OString> aAdjustments; |
4525 | 0 | auto it = aAdjMap.find(pShape); |
4526 | 0 | if (it != aAdjMap.end()) |
4527 | 0 | aAdjustments = it->second; |
4528 | |
|
4529 | 0 | mpFS->startElementNS(XML_a, XML_prstGeom, XML_prst, pShape); |
4530 | 0 | mpFS->startElementNS(XML_a, XML_avLst); |
4531 | |
|
4532 | 0 | Sequence< drawing::EnhancedCustomShapeAdjustmentValue > aAdjustmentSeq; |
4533 | 0 | if ( ( rProp.Value >>= aAdjustmentSeq ) |
4534 | 0 | && eShapeType != mso_sptActionButtonForwardNext // we have adjustments values for these type of shape, but MSO doesn't like them |
4535 | 0 | && eShapeType != mso_sptActionButtonBackPrevious // so they are now disabled |
4536 | 0 | && pShape != "rect" //some shape types are commented out in pCustomShapeTypeTranslationTable[] & are being defaulted to rect & rect does not have adjustment values/name. |
4537 | 0 | ) |
4538 | 0 | { |
4539 | 0 | SAL_INFO("oox.shape", "adj seq len: " << aAdjustmentSeq.getLength()); |
4540 | 0 | sal_Int32 nAdjustmentsWhichNeedsToBeConverted = 0; |
4541 | 0 | if ( bPredefinedHandlesUsed ) |
4542 | 0 | EscherPropertyContainer::LookForPolarHandles( eShapeType, nAdjustmentsWhichNeedsToBeConverted ); |
4543 | |
|
4544 | 0 | sal_Int32 nValue, nLength = aAdjustmentSeq.getLength(); |
4545 | | // aAdjustments will give info about the number of adj values for a particular geometry. For example for hexagon aAdjustments.size() will be 2 and for circular arrow it will be 5 as per lcl_getAdjNames. |
4546 | | // Sometimes there are more values than needed, so we ignore the excessive ones. |
4547 | 0 | if (aAdjustments.size() <= o3tl::make_unsigned(nLength)) |
4548 | 0 | { |
4549 | 0 | for (sal_Int32 i = 0; i < static_cast<sal_Int32>(aAdjustments.size()); i++) |
4550 | 0 | { |
4551 | 0 | if( EscherPropertyContainer::GetAdjustmentValue( aAdjustmentSeq[ i ], i, nAdjustmentsWhichNeedsToBeConverted, nValue ) ) |
4552 | 0 | { |
4553 | | // If the document model doesn't have an adjustment name (e.g. shape was created from VML), then take it from the predefined list. |
4554 | 0 | OString aAdjName = aAdjustmentSeq[i].Name.isEmpty() |
4555 | 0 | ? aAdjustments[i] |
4556 | 0 | : aAdjustmentSeq[i].Name.toUtf8(); |
4557 | |
|
4558 | 0 | mpFS->singleElementNS( XML_a, XML_gd, |
4559 | 0 | XML_name, aAdjName, |
4560 | 0 | XML_fmla, "val " + OString::number(nValue)); |
4561 | 0 | } |
4562 | 0 | } |
4563 | 0 | } |
4564 | 0 | } |
4565 | | |
4566 | 0 | mpFS->endElementNS( XML_a, XML_avLst ); |
4567 | 0 | mpFS->endElementNS( XML_a, XML_prstGeom ); |
4568 | 0 | } |
4569 | | |
4570 | | namespace // helpers for DrawingML::WriteCustomGeometry |
4571 | | { |
4572 | | sal_Int32 |
4573 | | FindNextCommandEndSubpath(const sal_Int32 nStart, |
4574 | | const uno::Sequence<drawing::EnhancedCustomShapeSegment>& rSegments) |
4575 | 0 | { |
4576 | 0 | sal_Int32 i = nStart < 0 ? 0 : nStart; |
4577 | 0 | while (i < rSegments.getLength() && rSegments[i].Command != ENDSUBPATH) |
4578 | 0 | i++; |
4579 | 0 | return i; |
4580 | 0 | } |
4581 | | |
4582 | | bool HasCommandInSubPath(const sal_Int16 nCommand, const sal_Int32 nFirst, const sal_Int32 nLast, |
4583 | | const uno::Sequence<drawing::EnhancedCustomShapeSegment>& rSegments) |
4584 | 0 | { |
4585 | 0 | for (sal_Int32 i = nFirst < 0 ? 0 : nFirst; i <= nLast && i < rSegments.getLength(); i++) |
4586 | 0 | { |
4587 | 0 | if (rSegments[i].Command == nCommand) |
4588 | 0 | return true; |
4589 | 0 | } |
4590 | 0 | return false; |
4591 | 0 | } |
4592 | | |
4593 | | // Ellipse is given by radii fwR and fhR and center (fCx|fCy). The ray from center through point RayP |
4594 | | // intersects the ellipse in point S and this point S has angle fAngleDeg in degrees. |
4595 | | void getEllipsePointAndAngleFromRayPoint(double& rfAngleDeg, double& rfSx, double& rfSy, |
4596 | | const double fWR, const double fHR, const double fCx, |
4597 | | const double fCy, const double fRayPx, const double fRayPy) |
4598 | 0 | { |
4599 | 0 | if (basegfx::fTools::equalZero(fWR) || basegfx::fTools::equalZero(fHR)) |
4600 | 0 | { |
4601 | 0 | rfSx = fCx; // needed for getting new 'current point' |
4602 | 0 | rfSy = fCy; |
4603 | 0 | } |
4604 | 0 | else |
4605 | 0 | { |
4606 | | // center ellipse at origin, stretch in y-direction to circle, flip to Math orientation |
4607 | | // and get angle |
4608 | 0 | double fCircleMathAngle = atan2(-fWR / fHR * (fRayPy - fCy), fRayPx - fCx); |
4609 | | // use angle for intersection point on circle and stretch back to ellipse |
4610 | 0 | double fPointMathEllipse_x = fWR * cos(fCircleMathAngle); |
4611 | 0 | double fPointMathEllipse_y = fHR * sin(fCircleMathAngle); |
4612 | | // get angle of intersection point on ellipse |
4613 | 0 | double fEllipseMathAngle = atan2(fPointMathEllipse_y, fPointMathEllipse_x); |
4614 | | // convert from Math to View orientation and shift ellipse back from origin |
4615 | 0 | rfAngleDeg = -basegfx::rad2deg(fEllipseMathAngle); |
4616 | 0 | rfSx = fPointMathEllipse_x + fCx; |
4617 | 0 | rfSy = -fPointMathEllipse_y + fCy; |
4618 | 0 | } |
4619 | 0 | } |
4620 | | |
4621 | | void getEllipsePointFromViewAngle(double& rfSx, double& rfSy, const double fWR, const double fHR, |
4622 | | const double fCx, const double fCy, const double fViewAngleDeg) |
4623 | 0 | { |
4624 | 0 | if (basegfx::fTools::equalZero(fWR) || basegfx::fTools::equalZero(fHR)) |
4625 | 0 | { |
4626 | 0 | rfSx = fCx; // needed for getting new 'current point' |
4627 | 0 | rfSy = fCy; |
4628 | 0 | } |
4629 | 0 | else |
4630 | 0 | { |
4631 | 0 | double fX = cos(basegfx::deg2rad(fViewAngleDeg)) / fWR; |
4632 | 0 | double fY = sin(basegfx::deg2rad(fViewAngleDeg)) / fHR; |
4633 | 0 | double fRadius = 1.0 / std::hypot(fX, fY); |
4634 | 0 | rfSx = fCx + fRadius * cos(basegfx::deg2rad(fViewAngleDeg)); |
4635 | 0 | rfSy = fCy + fRadius * sin(basegfx::deg2rad(fViewAngleDeg)); |
4636 | 0 | } |
4637 | 0 | } |
4638 | | |
4639 | | sal_Int32 GetCustomGeometryPointValue(const css::drawing::EnhancedCustomShapeParameter& rParam, |
4640 | | const EnhancedCustomShape2d& rCustomShape2d, |
4641 | | const bool bReplaceGeoWidth, const bool bReplaceGeoHeight) |
4642 | 0 | { |
4643 | 0 | double fValue = 0.0; |
4644 | 0 | rCustomShape2d.GetParameter(fValue, rParam, bReplaceGeoWidth, bReplaceGeoHeight); |
4645 | 0 | sal_Int32 nValue(std::lround(fValue)); |
4646 | |
|
4647 | 0 | return nValue; |
4648 | 0 | } |
4649 | | |
4650 | | struct TextAreaRect |
4651 | | { |
4652 | | OString left; |
4653 | | OString top; |
4654 | | OString right; |
4655 | | OString bottom; |
4656 | | }; |
4657 | | |
4658 | | struct Guide |
4659 | | { |
4660 | | OString sName; |
4661 | | OString sFormula; |
4662 | | }; |
4663 | | |
4664 | | void prepareTextArea(const EnhancedCustomShape2d& rEnhancedCustomShape2d, |
4665 | | std::vector<Guide>& rGuideList, TextAreaRect& rTextAreaRect) |
4666 | 0 | { |
4667 | 0 | tools::Rectangle aTextAreaLO(rEnhancedCustomShape2d.GetTextRect()); |
4668 | 0 | tools::Rectangle aLogicRectLO(rEnhancedCustomShape2d.GetLogicRect()); |
4669 | 0 | if (aTextAreaLO == aLogicRectLO) |
4670 | 0 | { |
4671 | 0 | rTextAreaRect.left = "l"_ostr; |
4672 | 0 | rTextAreaRect.top = "t"_ostr; |
4673 | 0 | rTextAreaRect.right = "r"_ostr; |
4674 | 0 | rTextAreaRect.bottom = "b"_ostr; |
4675 | 0 | return; |
4676 | 0 | } |
4677 | | // Flip aTextAreaLO if shape is flipped |
4678 | 0 | if (rEnhancedCustomShape2d.IsFlipHorz()) |
4679 | 0 | aTextAreaLO.Move((aLogicRectLO.Center().X() - aTextAreaLO.Center().X()) * 2, 0); |
4680 | 0 | if (rEnhancedCustomShape2d.IsFlipVert()) |
4681 | 0 | aTextAreaLO.Move(0, (aLogicRectLO.Center().Y() - aTextAreaLO.Center().Y()) * 2); |
4682 | |
|
4683 | 0 | Guide aGuide; |
4684 | | // horizontal |
4685 | 0 | const sal_Int32 nWidth = aLogicRectLO.Right() - aLogicRectLO.Left(); |
4686 | 0 | const OString sWidth = OString::number(oox::drawingml::convertHmmToEmu(nWidth)); |
4687 | | |
4688 | | // left |
4689 | 0 | aGuide.sName = "textAreaLeft"_ostr; |
4690 | 0 | sal_Int32 nHelp = aTextAreaLO.Left() - aLogicRectLO.Left(); |
4691 | 0 | const OString sLeft = OString::number(oox::drawingml::convertHmmToEmu(nHelp)); |
4692 | 0 | aGuide.sFormula = "*/ " + sLeft + " w " + sWidth; |
4693 | 0 | rTextAreaRect.left = aGuide.sName; |
4694 | 0 | rGuideList.push_back(aGuide); |
4695 | | |
4696 | | // right |
4697 | 0 | aGuide.sName = "textAreaRight"_ostr; |
4698 | 0 | nHelp = aTextAreaLO.Right() - aLogicRectLO.Left(); |
4699 | 0 | const OString sRight = OString::number(oox::drawingml::convertHmmToEmu(nHelp)); |
4700 | 0 | aGuide.sFormula = "*/ " + sRight + " w " + sWidth; |
4701 | 0 | rTextAreaRect.right = aGuide.sName; |
4702 | 0 | rGuideList.push_back(aGuide); |
4703 | | |
4704 | | // vertical |
4705 | 0 | const sal_Int32 nHeight = aLogicRectLO.Bottom() - aLogicRectLO.Top(); |
4706 | 0 | const OString sHeight = OString::number(oox::drawingml::convertHmmToEmu(nHeight)); |
4707 | | |
4708 | | // top |
4709 | 0 | aGuide.sName = "textAreaTop"_ostr; |
4710 | 0 | nHelp = aTextAreaLO.Top() - aLogicRectLO.Top(); |
4711 | 0 | const OString sTop = OString::number(oox::drawingml::convertHmmToEmu(nHelp)); |
4712 | 0 | aGuide.sFormula = "*/ " + sTop + " h " + sHeight; |
4713 | 0 | rTextAreaRect.top = aGuide.sName; |
4714 | 0 | rGuideList.push_back(aGuide); |
4715 | | |
4716 | | // bottom |
4717 | 0 | aGuide.sName = "textAreaBottom"_ostr; |
4718 | 0 | nHelp = aTextAreaLO.Bottom() - aLogicRectLO.Top(); |
4719 | 0 | const OString sBottom = OString::number(oox::drawingml::convertHmmToEmu(nHelp)); |
4720 | 0 | aGuide.sFormula = "*/ " + sBottom + " h " + sHeight; |
4721 | 0 | rTextAreaRect.bottom = aGuide.sName; |
4722 | 0 | rGuideList.push_back(aGuide); |
4723 | |
|
4724 | 0 | return; |
4725 | 0 | } |
4726 | | |
4727 | | OUString GetFormula(const OUString& sEquation, const OUString& sReplace, const OUString& sNewStr) |
4728 | 0 | { |
4729 | 0 | OUString sFormula = sEquation; |
4730 | 0 | size_t nPos = sFormula.indexOf(sReplace); |
4731 | 0 | if (nPos != std::string::npos) |
4732 | 0 | { |
4733 | 0 | OUString sModifiedEquation = sFormula.replaceAt(nPos, sReplace.getLength(), sNewStr); |
4734 | 0 | sFormula = "*/ " + sModifiedEquation; |
4735 | 0 | } |
4736 | |
|
4737 | 0 | return sFormula; |
4738 | 0 | } |
4739 | | |
4740 | | void prepareGluePoints(std::vector<Guide>& rGuideList, |
4741 | | const css::uno::Sequence<OUString>& aEquations, |
4742 | | const uno::Sequence<drawing::EnhancedCustomShapeParameterPair>& rGluePoints, |
4743 | | const bool bIsOOXML, const sal_Int32 nWidth, const sal_Int32 nHeight) |
4744 | 0 | { |
4745 | 0 | if (rGluePoints.hasElements()) |
4746 | 0 | { |
4747 | 0 | sal_Int32 nIndex = 1; |
4748 | 0 | for (auto const& rGluePoint : rGluePoints) |
4749 | 0 | { |
4750 | 0 | sal_Int32 nIdx1 = -1; |
4751 | 0 | sal_Int32 nIdx2 = -1; |
4752 | 0 | rGluePoint.First.Value >>= nIdx1; |
4753 | 0 | rGluePoint.Second.Value >>= nIdx2; |
4754 | |
|
4755 | 0 | if (nIdx1 != -1 && nIdx2 != -1) |
4756 | 0 | { |
4757 | 0 | Guide aGuideX; |
4758 | 0 | aGuideX.sName = "GluePoint"_ostr + OString::number(nIndex) + "X"; |
4759 | 0 | aGuideX.sFormula |
4760 | 0 | = (bIsOOXML && nIdx1 >= 0 && nIdx1 < aEquations.getLength()) |
4761 | 0 | ? GetFormula(aEquations[nIdx1], "*logwidth/", " w ").toUtf8() |
4762 | 0 | : "*/ " + OString::number(nIdx1) + " w " + OString::number(nWidth); |
4763 | 0 | rGuideList.push_back(aGuideX); |
4764 | |
|
4765 | 0 | Guide aGuideY; |
4766 | 0 | aGuideY.sName = "GluePoint"_ostr + OString::number(nIndex) + "Y"; |
4767 | 0 | aGuideY.sFormula |
4768 | 0 | = (bIsOOXML && nIdx2 >= 0 && nIdx2 < aEquations.getLength()) |
4769 | 0 | ? GetFormula(aEquations[nIdx2], "*logheight/", " h ").toUtf8() |
4770 | 0 | : "*/ " + OString::number(nIdx2) + " h " + OString::number(nHeight); |
4771 | 0 | rGuideList.push_back(aGuideY); |
4772 | 0 | } |
4773 | |
|
4774 | 0 | nIndex++; |
4775 | 0 | } |
4776 | 0 | } |
4777 | 0 | } |
4778 | | } |
4779 | | |
4780 | | bool DrawingML::WriteCustomGeometry( |
4781 | | const Reference< XShape >& rXShape, |
4782 | | const SdrObjCustomShape& rSdrObjCustomShape) |
4783 | 0 | { |
4784 | 0 | uno::Reference< beans::XPropertySet > aXPropSet; |
4785 | 0 | uno::Any aAny( rXShape->queryInterface(cppu::UnoType<beans::XPropertySet>::get())); |
4786 | |
|
4787 | 0 | if ( ! (aAny >>= aXPropSet) ) |
4788 | 0 | return false; |
4789 | | |
4790 | 0 | try |
4791 | 0 | { |
4792 | 0 | aAny = aXPropSet->getPropertyValue( u"CustomShapeGeometry"_ustr ); |
4793 | 0 | if ( !aAny.hasValue() ) |
4794 | 0 | return false; |
4795 | 0 | } |
4796 | 0 | catch( const ::uno::Exception& ) |
4797 | 0 | { |
4798 | 0 | return false; |
4799 | 0 | } |
4800 | | |
4801 | 0 | auto pGeometrySeq = o3tl::tryAccess<uno::Sequence<beans::PropertyValue>>(aAny); |
4802 | 0 | if (!pGeometrySeq) |
4803 | 0 | return false; |
4804 | | |
4805 | 0 | auto pPathProp = std::find_if(std::cbegin(*pGeometrySeq), std::cend(*pGeometrySeq), |
4806 | 0 | [](const PropertyValue& rProp) { return rProp.Name == "Path"; }); |
4807 | 0 | if (pPathProp == std::cend(*pGeometrySeq)) |
4808 | 0 | return false; |
4809 | | |
4810 | 0 | uno::Sequence<beans::PropertyValue> aPathProp; |
4811 | 0 | pPathProp->Value >>= aPathProp; |
4812 | |
|
4813 | 0 | auto pShapeType = std::find_if(std::cbegin(*pGeometrySeq), std::cend(*pGeometrySeq), |
4814 | 0 | [](const PropertyValue& rProp) { return rProp.Name == "Type"; }); |
4815 | 0 | bool bOOXML = false; |
4816 | 0 | if (pShapeType != std::cend(*pGeometrySeq)) |
4817 | 0 | { |
4818 | 0 | OUString sShapeType; |
4819 | 0 | pShapeType->Value >>= sShapeType; |
4820 | 0 | if (sShapeType.startsWith("ooxml")) |
4821 | 0 | bOOXML = true; |
4822 | 0 | } |
4823 | |
|
4824 | 0 | auto pEquationsProp |
4825 | 0 | = std::find_if(std::cbegin(*pGeometrySeq), std::cend(*pGeometrySeq), |
4826 | 0 | [](const PropertyValue& rProp) { return rProp.Name == "Equations"; }); |
4827 | |
|
4828 | 0 | css::uno::Sequence<OUString> aEquationSeq; |
4829 | 0 | if (pEquationsProp != std::cend(*pGeometrySeq)) |
4830 | 0 | { |
4831 | 0 | pEquationsProp->Value >>= aEquationSeq; |
4832 | 0 | } |
4833 | |
|
4834 | 0 | uno::Sequence<drawing::EnhancedCustomShapeParameterPair> aGluePoints; |
4835 | 0 | uno::Sequence<drawing::EnhancedCustomShapeParameterPair> aPairs; |
4836 | 0 | uno::Sequence<drawing::EnhancedCustomShapeSegment> aSegments; |
4837 | 0 | uno::Sequence<awt::Size> aPathSize; |
4838 | 0 | bool bReplaceGeoWidth = false; |
4839 | 0 | bool bReplaceGeoHeight = false; |
4840 | 0 | for (const beans::PropertyValue& rPathProp : aPathProp) |
4841 | 0 | { |
4842 | 0 | if (rPathProp.Name == "Coordinates") |
4843 | 0 | rPathProp.Value >>= aPairs; |
4844 | 0 | else if (rPathProp.Name == "Segments") |
4845 | 0 | rPathProp.Value >>= aSegments; |
4846 | 0 | else if (rPathProp.Name == "GluePoints") |
4847 | 0 | rPathProp.Value >>= aGluePoints; |
4848 | 0 | else if (rPathProp.Name == "SubViewSize") |
4849 | 0 | rPathProp.Value >>= aPathSize; |
4850 | 0 | else if (rPathProp.Name == "StretchX") |
4851 | 0 | bReplaceGeoWidth = true; |
4852 | 0 | else if (rPathProp.Name == "StretchY") |
4853 | 0 | bReplaceGeoHeight = true; |
4854 | 0 | } |
4855 | |
|
4856 | 0 | if ( !aPairs.hasElements() ) |
4857 | 0 | return false; |
4858 | | |
4859 | 0 | if ( !aSegments.hasElements() ) |
4860 | 0 | { |
4861 | 0 | aSegments = uno::Sequence<drawing::EnhancedCustomShapeSegment> |
4862 | 0 | { |
4863 | 0 | { MOVETO, 1 }, |
4864 | 0 | { LINETO, |
4865 | 0 | static_cast<sal_Int16>(std::min( aPairs.getLength() - 1, sal_Int32(32767) )) }, |
4866 | 0 | { CLOSESUBPATH, 0 }, |
4867 | 0 | { ENDSUBPATH, 0 } |
4868 | 0 | }; |
4869 | 0 | }; |
4870 | |
|
4871 | 0 | int nExpectedPairCount = std::accumulate(std::cbegin(aSegments), std::cend(aSegments), 0, |
4872 | 0 | [](const int nSum, const drawing::EnhancedCustomShapeSegment& rSegment) { return nSum + rSegment.Count; }); |
4873 | |
|
4874 | 0 | if ( nExpectedPairCount > aPairs.getLength() ) |
4875 | 0 | { |
4876 | 0 | SAL_WARN("oox.shape", "Segments need " << nExpectedPairCount << " coordinates, but Coordinates have only " << aPairs.getLength() << " pairs."); |
4877 | 0 | return false; |
4878 | 0 | } |
4879 | | |
4880 | | // A EnhancedCustomShape2d caches the equation results. Therefore we use only one of it for the |
4881 | | // entire method. |
4882 | 0 | const EnhancedCustomShape2d aCustomShape2d(const_cast<SdrObjCustomShape&>(rSdrObjCustomShape)); |
4883 | | |
4884 | | // Prepare width and height for <a:path> |
4885 | 0 | bool bUseGlobalViewBox(false); |
4886 | | |
4887 | | // nViewBoxWidth must be integer otherwise ReplaceGeoWidth in aCustomShape2d.GetParameter() is not |
4888 | | // triggered; same for height. |
4889 | 0 | sal_Int32 nViewBoxWidth(0); |
4890 | 0 | sal_Int32 nViewBoxHeight(0); |
4891 | 0 | if (!aPathSize.hasElements()) |
4892 | 0 | { |
4893 | 0 | bUseGlobalViewBox = true; |
4894 | | // If draw:viewBox is missing in draw:enhancedGeometry, then import sets |
4895 | | // viewBox="0 0 21600 21600". Missing ViewBox can only occur, if user has manipulated |
4896 | | // current file via macro. Author of macro has to fix it. |
4897 | 0 | auto pProp = std::find_if( |
4898 | 0 | std::cbegin(*pGeometrySeq), std::cend(*pGeometrySeq), |
4899 | 0 | [](const beans::PropertyValue& rGeomProp) { return rGeomProp.Name == "ViewBox"; }); |
4900 | 0 | if (pProp != std::cend(*pGeometrySeq)) |
4901 | 0 | { |
4902 | 0 | css::awt::Rectangle aViewBox; |
4903 | 0 | if (pProp->Value >>= aViewBox) |
4904 | 0 | { |
4905 | 0 | nViewBoxWidth = aViewBox.Width; |
4906 | 0 | nViewBoxHeight = aViewBox.Height; |
4907 | 0 | css::drawing::EnhancedCustomShapeParameter aECSP; |
4908 | 0 | aECSP.Type = css::drawing::EnhancedCustomShapeParameterType::NORMAL; |
4909 | 0 | aECSP.Value <<= nViewBoxWidth; |
4910 | 0 | double fRetValue; |
4911 | 0 | aCustomShape2d.GetParameter(fRetValue, aECSP, true, false); |
4912 | 0 | nViewBoxWidth = basegfx::fround(fRetValue); |
4913 | 0 | aECSP.Value <<= nViewBoxHeight; |
4914 | 0 | aCustomShape2d.GetParameter(fRetValue, aECSP, false, true); |
4915 | 0 | nViewBoxHeight = basegfx::fround(fRetValue); |
4916 | 0 | } |
4917 | 0 | } |
4918 | | // Import from oox or documents, which are imported from oox and saved to strict ODF, might |
4919 | | // have no subViewSize but viewBox="0 0 0 0". We need to generate width and height in those |
4920 | | // cases. Even if that is fixed, we need the substitute for old documents. |
4921 | 0 | if ((nViewBoxWidth == 0 && nViewBoxHeight == 0) || pProp == std::cend(*pGeometrySeq)) |
4922 | 0 | { |
4923 | | // Generate a substitute based on point coordinates |
4924 | 0 | sal_Int32 nXMin(0); |
4925 | 0 | aPairs[0].First.Value >>= nXMin; |
4926 | 0 | sal_Int32 nXMax = nXMin; |
4927 | 0 | sal_Int32 nYMin(0); |
4928 | 0 | aPairs[0].Second.Value >>= nYMin; |
4929 | 0 | sal_Int32 nYMax = nYMin; |
4930 | |
|
4931 | 0 | for (const auto& rPair : aPairs) |
4932 | 0 | { |
4933 | 0 | sal_Int32 nX = GetCustomGeometryPointValue(rPair.First, aCustomShape2d, |
4934 | 0 | bReplaceGeoWidth, false); |
4935 | 0 | sal_Int32 nY = GetCustomGeometryPointValue(rPair.Second, aCustomShape2d, false, |
4936 | 0 | bReplaceGeoHeight); |
4937 | 0 | if (nX < nXMin) |
4938 | 0 | nXMin = nX; |
4939 | 0 | if (nY < nYMin) |
4940 | 0 | nYMin = nY; |
4941 | 0 | if (nX > nXMax) |
4942 | 0 | nXMax = nX; |
4943 | 0 | if (nY > nYMax) |
4944 | 0 | nYMax = nY; |
4945 | 0 | } |
4946 | 0 | nViewBoxWidth = std::max(nXMax, nXMax - nXMin); |
4947 | 0 | nViewBoxHeight = std::max(nYMax, nYMax - nYMin); |
4948 | 0 | } |
4949 | | // ToDo: Other values of left,top than 0,0 are not considered yet. Such would require a |
4950 | | // shift of the resulting path coordinates. |
4951 | 0 | } |
4952 | |
|
4953 | 0 | TextAreaRect aTextAreaRect; |
4954 | 0 | std::vector<Guide> aGuideList; // for now only for <a:rect> |
4955 | 0 | prepareTextArea(aCustomShape2d, aGuideList, aTextAreaRect); |
4956 | 0 | prepareGluePoints(aGuideList, aEquationSeq, aGluePoints, bOOXML, nViewBoxWidth, nViewBoxHeight); |
4957 | 0 | mpFS->startElementNS(XML_a, XML_custGeom); |
4958 | 0 | mpFS->singleElementNS(XML_a, XML_avLst); |
4959 | 0 | if (aGuideList.empty()) |
4960 | 0 | { |
4961 | 0 | mpFS->singleElementNS(XML_a, XML_gdLst); |
4962 | 0 | } |
4963 | 0 | else |
4964 | 0 | { |
4965 | 0 | mpFS->startElementNS(XML_a, XML_gdLst); |
4966 | 0 | for (auto const& elem : aGuideList) |
4967 | 0 | { |
4968 | 0 | mpFS->singleElementNS(XML_a, XML_gd, XML_name, elem.sName, XML_fmla, elem.sFormula); |
4969 | 0 | } |
4970 | 0 | mpFS->endElementNS(XML_a, XML_gdLst); |
4971 | 0 | } |
4972 | 0 | mpFS->singleElementNS(XML_a, XML_ahLst); |
4973 | |
|
4974 | 0 | if (!aGuideList.empty()) |
4975 | 0 | { |
4976 | 0 | mpFS->startElementNS(XML_a, XML_cxnLst); |
4977 | 0 | for (auto it = aGuideList.begin(); it != aGuideList.end(); ++it) |
4978 | 0 | { |
4979 | 0 | auto aNextIt = std::next(it); |
4980 | 0 | if (aNextIt != aGuideList.end() && it->sName.startsWith("GluePoint") |
4981 | 0 | && aNextIt->sName.startsWith("GluePoint")) |
4982 | 0 | { |
4983 | 0 | mpFS->startElementNS(XML_a, XML_cxn, XML_ang, "0"); |
4984 | 0 | mpFS->singleElementNS(XML_a, XML_pos, XML_x, it->sName, XML_y, aNextIt->sName); |
4985 | 0 | mpFS->endElementNS(XML_a, XML_cxn); |
4986 | |
|
4987 | 0 | ++it; |
4988 | 0 | } |
4989 | 0 | } |
4990 | 0 | mpFS->endElementNS(XML_a, XML_cxnLst); |
4991 | 0 | } |
4992 | |
|
4993 | 0 | mpFS->singleElementNS(XML_a, XML_rect, XML_l, aTextAreaRect.left, XML_t, aTextAreaRect.top, |
4994 | 0 | XML_r, aTextAreaRect.right, XML_b, aTextAreaRect.bottom); |
4995 | 0 | mpFS->startElementNS(XML_a, XML_pathLst); |
4996 | | |
4997 | | // Iterate over subpaths |
4998 | 0 | sal_Int32 nPairIndex = 0; // index over "Coordinates" |
4999 | 0 | sal_Int32 nPathSizeIndex = 0; // index over "SubViewSize" |
5000 | 0 | sal_Int32 nSubpathStartIndex(0); // index over "Segments" |
5001 | 0 | sal_Int32 nSubPathIndex(0); // serial number of current subpath |
5002 | 0 | do |
5003 | 0 | { |
5004 | 0 | bool bOK(true); // catch faulty paths were commands do not correspond to points |
5005 | | // get index of next command ENDSUBPATH; if such doesn't exist use index behind last segment |
5006 | 0 | sal_Int32 nNextNcommandIndex = FindNextCommandEndSubpath(nSubpathStartIndex, aSegments); |
5007 | | |
5008 | | // Prepare attributes for a:path start element |
5009 | | // NOFILL or one of the LIGHTEN commands |
5010 | 0 | std::optional<OString> sFill; |
5011 | 0 | if (HasCommandInSubPath(NOFILL, nSubpathStartIndex, nNextNcommandIndex - 1, aSegments)) |
5012 | 0 | sFill = "none"; |
5013 | 0 | else if (HasCommandInSubPath(DARKEN, nSubpathStartIndex, nNextNcommandIndex - 1, aSegments)) |
5014 | 0 | sFill = "darken"; |
5015 | 0 | else if (HasCommandInSubPath(DARKENLESS, nSubpathStartIndex, nNextNcommandIndex - 1, |
5016 | 0 | aSegments)) |
5017 | 0 | sFill = "darkenLess"; |
5018 | 0 | else if (HasCommandInSubPath(LIGHTEN, nSubpathStartIndex, nNextNcommandIndex - 1, |
5019 | 0 | aSegments)) |
5020 | 0 | sFill = "lighten"; |
5021 | 0 | else if (HasCommandInSubPath(LIGHTENLESS, nSubpathStartIndex, nNextNcommandIndex - 1, |
5022 | 0 | aSegments)) |
5023 | 0 | sFill = "lightenLess"; |
5024 | 0 | else |
5025 | 0 | { |
5026 | | // shading info might be in object type, e.g. "Octagon Bevel". |
5027 | 0 | sal_Int32 nLuminanceChange(aCustomShape2d.GetLuminanceChange(nSubPathIndex)); |
5028 | 0 | if (nLuminanceChange <= -40) |
5029 | 0 | sFill = "darken"; |
5030 | 0 | else if (nLuminanceChange <= -10) |
5031 | 0 | sFill = "darkenLess"; |
5032 | 0 | else if (nLuminanceChange >= 40) |
5033 | 0 | sFill = "lighten"; |
5034 | 0 | else if (nLuminanceChange >= 10) |
5035 | 0 | sFill = "lightenLess"; |
5036 | 0 | } |
5037 | | // NOSTROKE |
5038 | 0 | std::optional<OString> sStroke; |
5039 | 0 | if (HasCommandInSubPath(NOSTROKE, nSubpathStartIndex, nNextNcommandIndex - 1, aSegments)) |
5040 | 0 | sStroke = "0"; |
5041 | | |
5042 | | // Write a:path start element |
5043 | 0 | mpFS->startElementNS( |
5044 | 0 | XML_a, XML_path, XML_fill, sFill, XML_stroke, sStroke, XML_w, |
5045 | 0 | OString::number(bUseGlobalViewBox ? nViewBoxWidth : aPathSize[nPathSizeIndex].Width), |
5046 | 0 | XML_h, |
5047 | 0 | OString::number(bUseGlobalViewBox ? nViewBoxHeight : aPathSize[nPathSizeIndex].Height)); |
5048 | | |
5049 | | // Arcs drawn by commands ELLIPTICALQUADRANTX and ELLIPTICALQUADRANTY depend on the position |
5050 | | // of the target point in regard to the current point. Therefore we need to track the |
5051 | | // current point. A current point is not defined in the beginning. |
5052 | 0 | double fCurrentX(0.0); |
5053 | 0 | double fCurrentY(0.0); |
5054 | 0 | bool bCurrentValid(false); |
5055 | | // Actually write the subpath |
5056 | 0 | for (sal_Int32 nSegmentIndex = nSubpathStartIndex; nSegmentIndex < nNextNcommandIndex; |
5057 | 0 | ++nSegmentIndex) |
5058 | 0 | { |
5059 | 0 | const auto& rSegment(aSegments[nSegmentIndex]); |
5060 | 0 | if (rSegment.Command == CLOSESUBPATH) |
5061 | 0 | { |
5062 | 0 | mpFS->singleElementNS(XML_a, XML_close); // command Z has no parameter |
5063 | | // ODF 1.4 specifies, that the start of the subpath becomes the current point. |
5064 | | // But that is not implemented yet. Currently LO keeps the last current point. |
5065 | 0 | } |
5066 | 0 | for (sal_Int32 k = 0; k < rSegment.Count && bOK; ++k) |
5067 | 0 | { |
5068 | 0 | bOK = WriteCustomGeometrySegment(rSegment.Command, k, aPairs, nPairIndex, fCurrentX, |
5069 | 0 | fCurrentY, bCurrentValid, aCustomShape2d, |
5070 | 0 | bReplaceGeoWidth, bReplaceGeoHeight); |
5071 | 0 | } |
5072 | 0 | } // end loop over all commands of subpath |
5073 | | // finish this subpath in any case |
5074 | 0 | mpFS->endElementNS(XML_a, XML_path); |
5075 | |
|
5076 | 0 | if (!bOK) |
5077 | 0 | break; // exit loop if not enough values in aPairs |
5078 | | |
5079 | | // step forward to next subpath |
5080 | 0 | nSubpathStartIndex = nNextNcommandIndex + 1; |
5081 | 0 | nPathSizeIndex++; |
5082 | 0 | nSubPathIndex++; |
5083 | 0 | } while (nSubpathStartIndex < aSegments.getLength()); |
5084 | | |
5085 | 0 | mpFS->endElementNS(XML_a, XML_pathLst); |
5086 | 0 | mpFS->endElementNS(XML_a, XML_custGeom); |
5087 | 0 | return true; // We have written custGeom even if path is poorly structured. |
5088 | 0 | } |
5089 | | |
5090 | | bool DrawingML::WriteCustomGeometrySegment( |
5091 | | const sal_Int16 eCommand, const sal_Int32 nCount, |
5092 | | const uno::Sequence<css::drawing::EnhancedCustomShapeParameterPair>& rPairs, |
5093 | | sal_Int32& rnPairIndex, double& rfCurrentX, double& rfCurrentY, bool& rbCurrentValid, |
5094 | | const EnhancedCustomShape2d& rCustomShape2d, const bool bReplaceGeoWidth, |
5095 | | const bool bReplaceGeoHeight) |
5096 | 0 | { |
5097 | 0 | switch (eCommand) |
5098 | 0 | { |
5099 | 0 | case MOVETO: |
5100 | 0 | { |
5101 | 0 | if (rnPairIndex >= rPairs.getLength()) |
5102 | 0 | return false; |
5103 | | |
5104 | 0 | mpFS->startElementNS(XML_a, XML_moveTo); |
5105 | 0 | WriteCustomGeometryPoint(rPairs[rnPairIndex], rCustomShape2d, bReplaceGeoWidth, |
5106 | 0 | bReplaceGeoHeight); |
5107 | 0 | mpFS->endElementNS(XML_a, XML_moveTo); |
5108 | 0 | rCustomShape2d.GetParameter(rfCurrentX, rPairs[rnPairIndex].First, bReplaceGeoWidth, |
5109 | 0 | false); |
5110 | 0 | rCustomShape2d.GetParameter(rfCurrentY, rPairs[rnPairIndex].Second, false, |
5111 | 0 | bReplaceGeoHeight); |
5112 | 0 | rbCurrentValid = true; |
5113 | 0 | rnPairIndex++; |
5114 | 0 | break; |
5115 | 0 | } |
5116 | 0 | case LINETO: |
5117 | 0 | { |
5118 | 0 | if (rnPairIndex >= rPairs.getLength()) |
5119 | 0 | return false; |
5120 | | // LINETO without valid current point is a faulty path. LO is tolerant and makes a |
5121 | | // moveTo instead. Do the same on export. MS OFFICE requires a current point for lnTo, |
5122 | | // otherwise it shows nothing of the shape. |
5123 | 0 | if (rbCurrentValid) |
5124 | 0 | { |
5125 | 0 | mpFS->startElementNS(XML_a, XML_lnTo); |
5126 | 0 | WriteCustomGeometryPoint(rPairs[rnPairIndex], rCustomShape2d, bReplaceGeoWidth, |
5127 | 0 | bReplaceGeoHeight); |
5128 | 0 | mpFS->endElementNS(XML_a, XML_lnTo); |
5129 | 0 | } |
5130 | 0 | else |
5131 | 0 | { |
5132 | 0 | mpFS->startElementNS(XML_a, XML_moveTo); |
5133 | 0 | WriteCustomGeometryPoint(rPairs[rnPairIndex], rCustomShape2d, bReplaceGeoWidth, |
5134 | 0 | bReplaceGeoHeight); |
5135 | 0 | mpFS->endElementNS(XML_a, XML_moveTo); |
5136 | 0 | } |
5137 | 0 | rCustomShape2d.GetParameter(rfCurrentX, rPairs[rnPairIndex].First, bReplaceGeoWidth, |
5138 | 0 | false); |
5139 | 0 | rCustomShape2d.GetParameter(rfCurrentY, rPairs[rnPairIndex].Second, false, |
5140 | 0 | bReplaceGeoHeight); |
5141 | 0 | rbCurrentValid = true; |
5142 | 0 | rnPairIndex++; |
5143 | 0 | break; |
5144 | 0 | } |
5145 | 0 | case CURVETO: |
5146 | 0 | { |
5147 | 0 | if (rnPairIndex + 2 >= rPairs.getLength()) |
5148 | 0 | return false; |
5149 | | |
5150 | 0 | mpFS->startElementNS(XML_a, XML_cubicBezTo); |
5151 | 0 | for (sal_uInt8 i = 0; i <= 2; ++i) |
5152 | 0 | { |
5153 | 0 | WriteCustomGeometryPoint(rPairs[rnPairIndex + i], rCustomShape2d, bReplaceGeoWidth, |
5154 | 0 | bReplaceGeoHeight); |
5155 | 0 | } |
5156 | 0 | mpFS->endElementNS(XML_a, XML_cubicBezTo); |
5157 | 0 | rCustomShape2d.GetParameter(rfCurrentX, rPairs[rnPairIndex + 2].First, bReplaceGeoWidth, |
5158 | 0 | false); |
5159 | 0 | rCustomShape2d.GetParameter(rfCurrentY, rPairs[rnPairIndex + 2].Second, false, |
5160 | 0 | bReplaceGeoHeight); |
5161 | 0 | rbCurrentValid = true; |
5162 | 0 | rnPairIndex += 3; |
5163 | 0 | break; |
5164 | 0 | } |
5165 | 0 | case ANGLEELLIPSETO: |
5166 | 0 | case ANGLEELLIPSE: |
5167 | 0 | { |
5168 | 0 | if (rnPairIndex + 2 >= rPairs.getLength()) |
5169 | 0 | return false; |
5170 | | |
5171 | | // Read parameters |
5172 | 0 | double fCx = 0.0; |
5173 | 0 | rCustomShape2d.GetParameter(fCx, rPairs[rnPairIndex].First, bReplaceGeoWidth, false); |
5174 | 0 | double fCy = 0.0; |
5175 | 0 | rCustomShape2d.GetParameter(fCy, rPairs[rnPairIndex].Second, false, bReplaceGeoHeight); |
5176 | 0 | double fWR = 0.0; |
5177 | 0 | rCustomShape2d.GetParameter(fWR, rPairs[rnPairIndex + 1].First, false, false); |
5178 | 0 | double fHR = 0.0; |
5179 | 0 | rCustomShape2d.GetParameter(fHR, rPairs[rnPairIndex + 1].Second, false, false); |
5180 | 0 | double fStartAngle = 0.0; |
5181 | 0 | rCustomShape2d.GetParameter(fStartAngle, rPairs[rnPairIndex + 2].First, false, false); |
5182 | 0 | double fEndAngle = 0.0; |
5183 | 0 | rCustomShape2d.GetParameter(fEndAngle, rPairs[rnPairIndex + 2].Second, false, false); |
5184 | | |
5185 | | // Prepare start and swing angle |
5186 | 0 | sal_Int32 nStartAng(std::lround(fStartAngle * 60000)); |
5187 | 0 | sal_Int32 nSwingAng = 0; |
5188 | 0 | if (basegfx::fTools::equalZero(fStartAngle) |
5189 | 0 | && basegfx::fTools::equalZero(fEndAngle - 360.0)) |
5190 | 0 | nSwingAng = 360 * 60000; // special case full circle |
5191 | 0 | else |
5192 | 0 | { |
5193 | 0 | nSwingAng = std::lround((fEndAngle - fStartAngle) * 60000); |
5194 | 0 | if (nSwingAng < 0) |
5195 | 0 | nSwingAng += 360 * 60000; |
5196 | 0 | } |
5197 | | |
5198 | | // calculate start point on ellipse |
5199 | 0 | double fSx = 0.0; |
5200 | 0 | double fSy = 0.0; |
5201 | 0 | getEllipsePointFromViewAngle(fSx, fSy, fWR, fHR, fCx, fCy, fStartAngle); |
5202 | | |
5203 | | // write markup for going to start point |
5204 | | // lnTo requires a valid current point |
5205 | 0 | if (eCommand == ANGLEELLIPSETO && rbCurrentValid) |
5206 | 0 | { |
5207 | 0 | mpFS->startElementNS(XML_a, XML_lnTo); |
5208 | 0 | mpFS->singleElementNS(XML_a, XML_pt, XML_x, OString::number(std::lround(fSx)), |
5209 | 0 | XML_y, OString::number(std::lround(fSy))); |
5210 | 0 | mpFS->endElementNS(XML_a, XML_lnTo); |
5211 | 0 | } |
5212 | 0 | else |
5213 | 0 | { |
5214 | 0 | mpFS->startElementNS(XML_a, XML_moveTo); |
5215 | 0 | mpFS->singleElementNS(XML_a, XML_pt, XML_x, OString::number(std::lround(fSx)), |
5216 | 0 | XML_y, OString::number(std::lround(fSy))); |
5217 | 0 | mpFS->endElementNS(XML_a, XML_moveTo); |
5218 | 0 | } |
5219 | | // write markup for arcTo |
5220 | 0 | if (!basegfx::fTools::equalZero(fWR) && !basegfx::fTools::equalZero(fHR)) |
5221 | 0 | mpFS->singleElement( |
5222 | 0 | FSNS(XML_a, XML_arcTo), XML_wR, OString::number(std::lround(fWR)), XML_hR, |
5223 | 0 | OString::number(std::lround(fHR)), XML_stAng, OString::number(nStartAng), |
5224 | 0 | XML_swAng, OString::number(nSwingAng)); |
5225 | |
|
5226 | 0 | getEllipsePointFromViewAngle(rfCurrentX, rfCurrentY, fWR, fHR, fCx, fCy, fEndAngle); |
5227 | 0 | rbCurrentValid = true; |
5228 | 0 | rnPairIndex += 3; |
5229 | 0 | break; |
5230 | 0 | } |
5231 | 0 | case ARCTO: |
5232 | 0 | case ARC: |
5233 | 0 | case CLOCKWISEARCTO: |
5234 | 0 | case CLOCKWISEARC: |
5235 | 0 | { |
5236 | 0 | if (rnPairIndex + 3 >= rPairs.getLength()) |
5237 | 0 | return false; |
5238 | | |
5239 | | // read parameters |
5240 | 0 | double fX1 = 0.0; |
5241 | 0 | rCustomShape2d.GetParameter(fX1, rPairs[rnPairIndex].First, bReplaceGeoWidth, false); |
5242 | 0 | double fY1 = 0.0; |
5243 | 0 | rCustomShape2d.GetParameter(fY1, rPairs[rnPairIndex].Second, false, bReplaceGeoHeight); |
5244 | 0 | double fX2 = 0.0; |
5245 | 0 | rCustomShape2d.GetParameter(fX2, rPairs[rnPairIndex + 1].First, bReplaceGeoWidth, |
5246 | 0 | false); |
5247 | 0 | double fY2 = 0.0; |
5248 | 0 | rCustomShape2d.GetParameter(fY2, rPairs[rnPairIndex + 1].Second, false, |
5249 | 0 | bReplaceGeoHeight); |
5250 | 0 | double fX3 = 0.0; |
5251 | 0 | rCustomShape2d.GetParameter(fX3, rPairs[rnPairIndex + 2].First, bReplaceGeoWidth, |
5252 | 0 | false); |
5253 | 0 | double fY3 = 0.0; |
5254 | 0 | rCustomShape2d.GetParameter(fY3, rPairs[rnPairIndex + 2].Second, false, |
5255 | 0 | bReplaceGeoHeight); |
5256 | 0 | double fX4 = 0.0; |
5257 | 0 | rCustomShape2d.GetParameter(fX4, rPairs[rnPairIndex + 3].First, bReplaceGeoWidth, |
5258 | 0 | false); |
5259 | 0 | double fY4 = 0.0; |
5260 | 0 | rCustomShape2d.GetParameter(fY4, rPairs[rnPairIndex + 3].Second, false, |
5261 | 0 | bReplaceGeoHeight); |
5262 | | // calculate ellipse parameter |
5263 | 0 | const double fWR = (std::max(fX1, fX2) - std::min(fX1, fX2)) / 2.0; |
5264 | 0 | const double fHR = (std::max(fY1, fY2) - std::min(fY1, fY2)) / 2.0; |
5265 | 0 | const double fCx = (fX1 + fX2) / 2.0; |
5266 | 0 | const double fCy = (fY1 + fY2) / 2.0; |
5267 | | // calculate start angle |
5268 | 0 | double fStartAngle = 0.0; |
5269 | 0 | double fPx = 0.0; |
5270 | 0 | double fPy = 0.0; |
5271 | 0 | getEllipsePointAndAngleFromRayPoint(fStartAngle, fPx, fPy, fWR, fHR, fCx, fCy, fX3, |
5272 | 0 | fY3); |
5273 | | // markup for going to start point |
5274 | | // lnTo requires a valid current point. |
5275 | 0 | if ((eCommand == ARCTO || eCommand == CLOCKWISEARCTO) && rbCurrentValid) |
5276 | 0 | { |
5277 | 0 | mpFS->startElementNS(XML_a, XML_lnTo); |
5278 | 0 | mpFS->singleElementNS(XML_a, XML_pt, XML_x, OString::number(std::lround(fPx)), |
5279 | 0 | XML_y, OString::number(std::lround(fPy))); |
5280 | 0 | mpFS->endElementNS(XML_a, XML_lnTo); |
5281 | 0 | } |
5282 | 0 | else |
5283 | 0 | { |
5284 | 0 | mpFS->startElementNS(XML_a, XML_moveTo); |
5285 | 0 | mpFS->singleElementNS(XML_a, XML_pt, XML_x, OString::number(std::lround(fPx)), |
5286 | 0 | XML_y, OString::number(std::lround(fPy))); |
5287 | 0 | mpFS->endElementNS(XML_a, XML_moveTo); |
5288 | 0 | } |
5289 | | // calculate swing angle |
5290 | 0 | double fEndAngle = 0.0; |
5291 | 0 | getEllipsePointAndAngleFromRayPoint(fEndAngle, fPx, fPy, fWR, fHR, fCx, fCy, fX4, fY4); |
5292 | 0 | double fSwingAngle(fEndAngle - fStartAngle); |
5293 | 0 | const bool bIsClockwise(eCommand == CLOCKWISEARCTO || eCommand == CLOCKWISEARC); |
5294 | 0 | if (bIsClockwise && fSwingAngle < 0) |
5295 | 0 | fSwingAngle += 360.0; |
5296 | 0 | else if (!bIsClockwise && fSwingAngle > 0) |
5297 | 0 | fSwingAngle -= 360.0; |
5298 | | // markup for arcTo |
5299 | | // ToDo: write markup for case zero width or height of ellipse |
5300 | 0 | const sal_Int32 nStartAng(std::lround(fStartAngle * 60000)); |
5301 | 0 | const sal_Int32 nSwingAng(std::lround(fSwingAngle * 60000)); |
5302 | 0 | mpFS->singleElement(FSNS(XML_a, XML_arcTo), XML_wR, OString::number(std::lround(fWR)), |
5303 | 0 | XML_hR, OString::number(std::lround(fHR)), XML_stAng, |
5304 | 0 | OString::number(nStartAng), XML_swAng, OString::number(nSwingAng)); |
5305 | 0 | rfCurrentX = fPx; |
5306 | 0 | rfCurrentY = fPy; |
5307 | 0 | rbCurrentValid = true; |
5308 | 0 | rnPairIndex += 4; |
5309 | 0 | break; |
5310 | 0 | } |
5311 | 0 | case ELLIPTICALQUADRANTX: |
5312 | 0 | case ELLIPTICALQUADRANTY: |
5313 | 0 | { |
5314 | 0 | if (rnPairIndex >= rPairs.getLength()) |
5315 | 0 | return false; |
5316 | | |
5317 | | // read parameters |
5318 | 0 | double fX = 0.0; |
5319 | 0 | rCustomShape2d.GetParameter(fX, rPairs[rnPairIndex].First, bReplaceGeoWidth, false); |
5320 | 0 | double fY = 0.0; |
5321 | 0 | rCustomShape2d.GetParameter(fY, rPairs[rnPairIndex].Second, false, bReplaceGeoHeight); |
5322 | | |
5323 | | // Prepare parameters for arcTo |
5324 | 0 | if (rbCurrentValid) |
5325 | 0 | { |
5326 | 0 | double fWR = std::abs(rfCurrentX - fX); |
5327 | 0 | double fHR = std::abs(rfCurrentY - fY); |
5328 | 0 | double fStartAngle(0.0); |
5329 | 0 | double fSwingAngle(0.0); |
5330 | | // The starting direction of the arc toggles between X and Y |
5331 | 0 | if ((eCommand == ELLIPTICALQUADRANTX && !(nCount % 2)) |
5332 | 0 | || (eCommand == ELLIPTICALQUADRANTY && (nCount % 2))) |
5333 | 0 | { |
5334 | | // arc starts horizontal |
5335 | 0 | fStartAngle = fY < rfCurrentY ? 90.0 : 270.0; |
5336 | 0 | const bool bClockwise = (fX < rfCurrentX && fY < rfCurrentY) |
5337 | 0 | || (fX > rfCurrentX && fY > rfCurrentY); |
5338 | 0 | fSwingAngle = bClockwise ? 90.0 : -90.0; |
5339 | 0 | } |
5340 | 0 | else |
5341 | 0 | { |
5342 | | // arc starts vertical |
5343 | 0 | fStartAngle = fX < rfCurrentX ? 0.0 : 180.0; |
5344 | 0 | const bool bClockwise = (fX < rfCurrentX && fY > rfCurrentY) |
5345 | 0 | || (fX > rfCurrentX && fY < rfCurrentY); |
5346 | 0 | fSwingAngle = bClockwise ? 90.0 : -90.0; |
5347 | 0 | } |
5348 | 0 | sal_Int32 nStartAng(std::lround(fStartAngle * 60000)); |
5349 | 0 | sal_Int32 nSwingAng(std::lround(fSwingAngle * 60000)); |
5350 | 0 | mpFS->singleElement( |
5351 | 0 | FSNS(XML_a, XML_arcTo), XML_wR, OString::number(std::lround(fWR)), XML_hR, |
5352 | 0 | OString::number(std::lround(fHR)), XML_stAng, OString::number(nStartAng), |
5353 | 0 | XML_swAng, OString::number(nSwingAng)); |
5354 | 0 | } |
5355 | 0 | else |
5356 | 0 | { |
5357 | | // faulty path, but we continue with the target point |
5358 | 0 | mpFS->startElementNS(XML_a, XML_moveTo); |
5359 | 0 | WriteCustomGeometryPoint(rPairs[rnPairIndex], rCustomShape2d, bReplaceGeoWidth, |
5360 | 0 | bReplaceGeoHeight); |
5361 | 0 | mpFS->endElementNS(XML_a, XML_moveTo); |
5362 | 0 | } |
5363 | 0 | rfCurrentX = fX; |
5364 | 0 | rfCurrentY = fY; |
5365 | 0 | rbCurrentValid = true; |
5366 | 0 | rnPairIndex++; |
5367 | 0 | break; |
5368 | 0 | } |
5369 | 0 | case QUADRATICCURVETO: |
5370 | 0 | { |
5371 | 0 | if (rnPairIndex + 1 >= rPairs.getLength()) |
5372 | 0 | return false; |
5373 | | |
5374 | 0 | mpFS->startElementNS(XML_a, XML_quadBezTo); |
5375 | 0 | for (sal_uInt8 i = 0; i < 2; ++i) |
5376 | 0 | { |
5377 | 0 | WriteCustomGeometryPoint(rPairs[rnPairIndex + i], rCustomShape2d, bReplaceGeoWidth, |
5378 | 0 | bReplaceGeoHeight); |
5379 | 0 | } |
5380 | 0 | mpFS->endElementNS(XML_a, XML_quadBezTo); |
5381 | 0 | rCustomShape2d.GetParameter(rfCurrentX, rPairs[rnPairIndex + 1].First, bReplaceGeoWidth, |
5382 | 0 | false); |
5383 | 0 | rCustomShape2d.GetParameter(rfCurrentY, rPairs[rnPairIndex + 1].Second, false, |
5384 | 0 | bReplaceGeoHeight); |
5385 | 0 | rbCurrentValid = true; |
5386 | 0 | rnPairIndex += 2; |
5387 | 0 | break; |
5388 | 0 | } |
5389 | 0 | case ARCANGLETO: |
5390 | 0 | { |
5391 | 0 | if (rnPairIndex + 1 >= rPairs.getLength()) |
5392 | 0 | return false; |
5393 | | |
5394 | 0 | double fWR = 0.0; |
5395 | 0 | rCustomShape2d.GetParameter(fWR, rPairs[rnPairIndex].First, false, false); |
5396 | 0 | double fHR = 0.0; |
5397 | 0 | rCustomShape2d.GetParameter(fHR, rPairs[rnPairIndex].Second, false, false); |
5398 | 0 | double fStartAngle = 0.0; |
5399 | 0 | rCustomShape2d.GetParameter(fStartAngle, rPairs[rnPairIndex + 1].First, false, false); |
5400 | 0 | sal_Int32 nStartAng(std::lround(fStartAngle * 60000)); |
5401 | 0 | double fSwingAng = 0.0; |
5402 | 0 | rCustomShape2d.GetParameter(fSwingAng, rPairs[rnPairIndex + 1].Second, false, false); |
5403 | 0 | sal_Int32 nSwingAng(std::lround(fSwingAng * 60000)); |
5404 | 0 | mpFS->singleElement(FSNS(XML_a, XML_arcTo), XML_wR, OString::number(fWR), XML_hR, |
5405 | 0 | OString::number(fHR), XML_stAng, OString::number(nStartAng), |
5406 | 0 | XML_swAng, OString::number(nSwingAng)); |
5407 | 0 | double fPx = 0.0; |
5408 | 0 | double fPy = 0.0; |
5409 | 0 | getEllipsePointFromViewAngle(fPx, fPy, fWR, fHR, 0.0, 0.0, fStartAngle); |
5410 | 0 | double fCx = rfCurrentX - fPx; |
5411 | 0 | double fCy = rfCurrentY - fPy; |
5412 | 0 | getEllipsePointFromViewAngle(rfCurrentX, rfCurrentY, fWR, fHR, fCx, fCy, |
5413 | 0 | fStartAngle + fSwingAng); |
5414 | 0 | rbCurrentValid = true; |
5415 | 0 | rnPairIndex += 2; |
5416 | 0 | break; |
5417 | 0 | } |
5418 | 0 | default: |
5419 | | // do nothing |
5420 | 0 | break; |
5421 | 0 | } |
5422 | 0 | return true; |
5423 | 0 | } |
5424 | | |
5425 | | void DrawingML::WriteCustomGeometryPoint( |
5426 | | const drawing::EnhancedCustomShapeParameterPair& rParamPair, |
5427 | | const EnhancedCustomShape2d& rCustomShape2d, const bool bReplaceGeoWidth, |
5428 | | const bool bReplaceGeoHeight) |
5429 | 0 | { |
5430 | 0 | sal_Int32 nX |
5431 | 0 | = GetCustomGeometryPointValue(rParamPair.First, rCustomShape2d, bReplaceGeoWidth, false); |
5432 | 0 | sal_Int32 nY |
5433 | 0 | = GetCustomGeometryPointValue(rParamPair.Second, rCustomShape2d, false, bReplaceGeoHeight); |
5434 | |
|
5435 | 0 | mpFS->singleElementNS(XML_a, XML_pt, XML_x, OString::number(nX), XML_y, OString::number(nY)); |
5436 | 0 | } |
5437 | | |
5438 | | void DrawingML::WriteEmptyCustomGeometry() |
5439 | 0 | { |
5440 | | // This method is used for export to docx in case WriteCustomGeometry fails. |
5441 | 0 | mpFS->startElementNS(XML_a, XML_custGeom); |
5442 | 0 | mpFS->singleElementNS(XML_a, XML_avLst); |
5443 | 0 | mpFS->singleElementNS(XML_a, XML_gdLst); |
5444 | 0 | mpFS->singleElementNS(XML_a, XML_ahLst); |
5445 | 0 | mpFS->singleElementNS(XML_a, XML_rect, XML_l, "0", XML_t, "0", XML_r, "r", XML_b, "b"); |
5446 | 0 | mpFS->singleElementNS(XML_a, XML_pathLst); |
5447 | 0 | mpFS->endElementNS(XML_a, XML_custGeom); |
5448 | 0 | } |
5449 | | |
5450 | | // version for SdrPathObj |
5451 | | void DrawingML::WritePolyPolygon(const css::uno::Reference<css::drawing::XShape>& rXShape, |
5452 | | const bool bClosed) |
5453 | 0 | { |
5454 | 0 | tools::PolyPolygon aPolyPolygon = EscherPropertyContainer::GetPolyPolygon(rXShape); |
5455 | | // In case of Writer, the parent element is <wps:spPr>, and there the |
5456 | | // <a:custGeom> element is not optional. |
5457 | 0 | if (aPolyPolygon.Count() < 1 && GetDocumentType() != DOCUMENT_DOCX) |
5458 | 0 | return; |
5459 | | |
5460 | 0 | mpFS->startElementNS(XML_a, XML_custGeom); |
5461 | 0 | mpFS->singleElementNS(XML_a, XML_avLst); |
5462 | 0 | mpFS->singleElementNS(XML_a, XML_gdLst); |
5463 | 0 | mpFS->singleElementNS(XML_a, XML_ahLst); |
5464 | 0 | mpFS->singleElementNS(XML_a, XML_rect, XML_l, "0", XML_t, "0", XML_r, "r", XML_b, "b"); |
5465 | |
|
5466 | 0 | mpFS->startElementNS(XML_a, XML_pathLst); |
5467 | |
|
5468 | 0 | awt::Size aSize = rXShape->getSize(); |
5469 | 0 | awt::Point aPos = rXShape->getPosition(); |
5470 | 0 | Reference<XPropertySet> xPropertySet(rXShape, UNO_QUERY); |
5471 | 0 | uno::Reference<XPropertySetInfo> xPropertySetInfo = xPropertySet->getPropertySetInfo(); |
5472 | 0 | if (xPropertySetInfo->hasPropertyByName(u"AnchorPosition"_ustr)) |
5473 | 0 | { |
5474 | 0 | awt::Point aAnchorPosition; |
5475 | 0 | xPropertySet->getPropertyValue(u"AnchorPosition"_ustr) >>= aAnchorPosition; |
5476 | 0 | aPos.X += aAnchorPosition.X; |
5477 | 0 | aPos.Y += aAnchorPosition.Y; |
5478 | 0 | } |
5479 | | |
5480 | | // Only closed SdrPathObj can be filled |
5481 | 0 | std::optional<OString> sFill; |
5482 | 0 | if (!bClosed) |
5483 | 0 | sFill = "none"; // for possible values see ST_PathFillMode in OOXML standard |
5484 | | |
5485 | | // Put all polygons of rPolyPolygon in the same path element |
5486 | | // to subtract the overlapped areas. |
5487 | 0 | mpFS->startElementNS(XML_a, XML_path, XML_fill, sFill, XML_w, OString::number(aSize.Width), |
5488 | 0 | XML_h, OString::number(aSize.Height)); |
5489 | |
|
5490 | 0 | for (sal_uInt16 i = 0; i < aPolyPolygon.Count(); i++) |
5491 | 0 | { |
5492 | 0 | const tools::Polygon& aPoly = aPolyPolygon[i]; |
5493 | |
|
5494 | 0 | if (aPoly.GetSize() > 0) |
5495 | 0 | { |
5496 | 0 | mpFS->startElementNS(XML_a, XML_moveTo); |
5497 | |
|
5498 | 0 | mpFS->singleElementNS(XML_a, XML_pt, XML_x, OString::number(aPoly[0].X() - aPos.X), |
5499 | 0 | XML_y, OString::number(aPoly[0].Y() - aPos.Y)); |
5500 | |
|
5501 | 0 | mpFS->endElementNS(XML_a, XML_moveTo); |
5502 | 0 | } |
5503 | |
|
5504 | 0 | for (sal_uInt16 j = 1; j < aPoly.GetSize(); j++) |
5505 | 0 | { |
5506 | 0 | PolyFlags flags = aPoly.GetFlags(j); |
5507 | 0 | if (flags == PolyFlags::Control) |
5508 | 0 | { |
5509 | | // a:cubicBezTo can only contain 3 a:pt elements, so we need to make sure of this |
5510 | 0 | if (j + 2 < aPoly.GetSize() && aPoly.GetFlags(j + 1) == PolyFlags::Control |
5511 | 0 | && aPoly.GetFlags(j + 2) != PolyFlags::Control) |
5512 | 0 | { |
5513 | 0 | mpFS->startElementNS(XML_a, XML_cubicBezTo); |
5514 | 0 | for (sal_uInt32 k = 0; k <= 2; ++k) |
5515 | 0 | { |
5516 | 0 | mpFS->singleElementNS(XML_a, XML_pt, XML_x, |
5517 | 0 | OString::number(aPoly[j + k].X() - aPos.X), XML_y, |
5518 | 0 | OString::number(aPoly[j + k].Y() - aPos.Y)); |
5519 | 0 | } |
5520 | 0 | mpFS->endElementNS(XML_a, XML_cubicBezTo); |
5521 | 0 | j += 2; |
5522 | 0 | } |
5523 | 0 | } |
5524 | 0 | else if (flags == PolyFlags::Normal) |
5525 | 0 | { |
5526 | 0 | mpFS->startElementNS(XML_a, XML_lnTo); |
5527 | 0 | mpFS->singleElementNS(XML_a, XML_pt, XML_x, OString::number(aPoly[j].X() - aPos.X), |
5528 | 0 | XML_y, OString::number(aPoly[j].Y() - aPos.Y)); |
5529 | 0 | mpFS->endElementNS(XML_a, XML_lnTo); |
5530 | 0 | } |
5531 | 0 | } |
5532 | 0 | } |
5533 | 0 | if (bClosed) |
5534 | 0 | mpFS->singleElementNS(XML_a, XML_close); |
5535 | 0 | mpFS->endElementNS(XML_a, XML_path); |
5536 | |
|
5537 | 0 | mpFS->endElementNS(XML_a, XML_pathLst); |
5538 | |
|
5539 | 0 | mpFS->endElementNS(XML_a, XML_custGeom); |
5540 | 0 | } |
5541 | | |
5542 | | void DrawingML::WriteConnectorConnections( sal_Int32 nStartGlueId, sal_Int32 nEndGlueId, sal_Int32 nStartID, sal_Int32 nEndID ) |
5543 | 0 | { |
5544 | 0 | if( nStartID != -1 ) |
5545 | 0 | { |
5546 | 0 | mpFS->singleElementNS( XML_a, XML_stCxn, |
5547 | 0 | XML_id, OString::number(nStartID), |
5548 | 0 | XML_idx, OString::number(nStartGlueId) ); |
5549 | 0 | } |
5550 | 0 | if( nEndID != -1 ) |
5551 | 0 | { |
5552 | 0 | mpFS->singleElementNS( XML_a, XML_endCxn, |
5553 | 0 | XML_id, OString::number(nEndID), |
5554 | 0 | XML_idx, OString::number(nEndGlueId) ); |
5555 | 0 | } |
5556 | 0 | } |
5557 | | |
5558 | | sal_Unicode DrawingML::SubstituteBullet( sal_Unicode cBulletId, css::awt::FontDescriptor& rFontDesc ) |
5559 | 0 | { |
5560 | 0 | if ( IsOpenSymbol(rFontDesc.Name) ) |
5561 | 0 | { |
5562 | 0 | rtl_TextEncoding eCharSet = rFontDesc.CharSet; |
5563 | 0 | cBulletId = msfilter::util::bestFitOpenSymbolToMSFont(cBulletId, eCharSet, rFontDesc.Name); |
5564 | 0 | rFontDesc.CharSet = eCharSet; |
5565 | 0 | } |
5566 | |
|
5567 | 0 | return cBulletId; |
5568 | 0 | } |
5569 | | |
5570 | | sax_fastparser::FSHelperPtr DrawingML::CreateOutputStream ( |
5571 | | const OUString& sFullStream, |
5572 | | std::u16string_view sRelativeStream, |
5573 | | const Reference< XOutputStream >& xParentRelation, |
5574 | | const OUString& sContentType, |
5575 | | const OUString& sRelationshipType, |
5576 | | OUString* pRelationshipId, |
5577 | | // if bNoHeader is true, don't create a header (<?xml... ) line |
5578 | | bool bNoHeader /* = false */ ) |
5579 | 0 | { |
5580 | 0 | OUString sRelationshipId; |
5581 | 0 | if (xParentRelation.is()) |
5582 | 0 | sRelationshipId = GetFB()->addRelation( xParentRelation, sRelationshipType, sRelativeStream ); |
5583 | 0 | else |
5584 | 0 | sRelationshipId = GetFB()->addRelation( sRelationshipType, sRelativeStream ); |
5585 | |
|
5586 | 0 | if( pRelationshipId ) |
5587 | 0 | *pRelationshipId = sRelationshipId; |
5588 | |
|
5589 | 0 | sax_fastparser::FSHelperPtr p = GetFB()->openFragmentStreamWithSerializer( |
5590 | 0 | sFullStream, sContentType, bNoHeader ); |
5591 | |
|
5592 | 0 | return p; |
5593 | 0 | } |
5594 | | |
5595 | | void DrawingML::WriteFill(const Reference<XPropertySet>& xPropSet, const awt::Size& rSize) |
5596 | 0 | { |
5597 | 0 | if ( !GetProperty( xPropSet, u"FillStyle"_ustr ) ) |
5598 | 0 | return; |
5599 | 0 | FillStyle aFillStyle( FillStyle_NONE ); |
5600 | 0 | xPropSet->getPropertyValue( u"FillStyle"_ustr ) >>= aFillStyle; |
5601 | | |
5602 | | // map full transparent background to no fill |
5603 | 0 | if (aFillStyle == FillStyle_SOLID) |
5604 | 0 | { |
5605 | 0 | OUString sFillTransparenceGradientName; |
5606 | |
|
5607 | 0 | if (GetProperty(xPropSet, u"FillTransparenceGradientName"_ustr) |
5608 | 0 | && (mAny >>= sFillTransparenceGradientName) |
5609 | 0 | && !sFillTransparenceGradientName.isEmpty() |
5610 | 0 | && GetProperty(xPropSet, u"FillTransparenceGradient"_ustr)) |
5611 | 0 | { |
5612 | | // check if a fully transparent TransparenceGradient is used |
5613 | | // use BGradient constructor & tooling here now |
5614 | 0 | const basegfx::BGradient aTransparenceGradient = model::gradient::getFromAny(mAny); |
5615 | 0 | basegfx::BColor aSingleColor; |
5616 | 0 | const bool bSingleColor(aTransparenceGradient.GetColorStops().isSingleColor(aSingleColor)); |
5617 | 0 | const bool bCompletelyTransparent(bSingleColor && basegfx::fTools::equal(aSingleColor.luminance(), 1.0)); |
5618 | |
|
5619 | 0 | if (bCompletelyTransparent) |
5620 | 0 | { |
5621 | 0 | aFillStyle = FillStyle_NONE; |
5622 | 0 | } |
5623 | 0 | } |
5624 | 0 | else if ( GetProperty( xPropSet, u"FillTransparence"_ustr ) ) |
5625 | 0 | { |
5626 | | // check if a fully transparent FillTransparence is used |
5627 | 0 | sal_Int16 nVal = 0; |
5628 | 0 | xPropSet->getPropertyValue( u"FillTransparence"_ustr ) >>= nVal; |
5629 | 0 | if ( nVal == 100 ) |
5630 | 0 | aFillStyle = FillStyle_NONE; |
5631 | 0 | } |
5632 | 0 | } |
5633 | |
|
5634 | 0 | bool bUseBackground(false); |
5635 | 0 | if (GetProperty(xPropSet, u"FillUseSlideBackground"_ustr)) |
5636 | 0 | xPropSet->getPropertyValue(u"FillUseSlideBackground"_ustr) >>= bUseBackground; |
5637 | |
|
5638 | 0 | switch( aFillStyle ) |
5639 | 0 | { |
5640 | 0 | case FillStyle_SOLID : |
5641 | 0 | WriteSolidFill( xPropSet ); |
5642 | 0 | break; |
5643 | 0 | case FillStyle_GRADIENT : |
5644 | 0 | WriteGradientFill( xPropSet ); |
5645 | 0 | break; |
5646 | 0 | case FillStyle_BITMAP : |
5647 | 0 | WriteBlipFill( xPropSet, u"FillBitmap"_ustr, rSize ); |
5648 | 0 | break; |
5649 | 0 | case FillStyle_HATCH : |
5650 | 0 | WritePattFill( xPropSet ); |
5651 | 0 | break; |
5652 | 0 | case FillStyle_NONE: |
5653 | 0 | if (!bUseBackground) // attribute `useBgFill` will be written at parent p:sp shape |
5654 | 0 | mpFS->singleElementNS(XML_a, XML_noFill); |
5655 | 0 | break; |
5656 | 0 | default: |
5657 | 0 | ; |
5658 | 0 | } |
5659 | 0 | } |
5660 | | |
5661 | | void DrawingML::WriteStyleProperties( sal_Int32 nTokenId, const Sequence< PropertyValue >& aProperties ) |
5662 | 0 | { |
5663 | 0 | if( aProperties.hasElements() ) |
5664 | 0 | { |
5665 | 0 | OUString sSchemeClr; |
5666 | 0 | sal_uInt32 nIdx = 0; |
5667 | 0 | Sequence< PropertyValue > aTransformations; |
5668 | 0 | for( const auto& rProp : aProperties) |
5669 | 0 | { |
5670 | 0 | if( rProp.Name == "SchemeClr" ) |
5671 | 0 | rProp.Value >>= sSchemeClr; |
5672 | 0 | else if( rProp.Name == "Idx" ) |
5673 | 0 | rProp.Value >>= nIdx; |
5674 | 0 | else if( rProp.Name == "Transformations" ) |
5675 | 0 | rProp.Value >>= aTransformations; |
5676 | 0 | } |
5677 | 0 | mpFS->startElementNS(XML_a, nTokenId, XML_idx, OString::number(nIdx)); |
5678 | 0 | WriteColor(sSchemeClr, aTransformations); |
5679 | 0 | mpFS->endElementNS( XML_a, nTokenId ); |
5680 | 0 | } |
5681 | 0 | else |
5682 | 0 | { |
5683 | | // write mock <a:*Ref> tag |
5684 | 0 | mpFS->singleElementNS(XML_a, nTokenId, XML_idx, OString::number(0)); |
5685 | 0 | } |
5686 | 0 | } |
5687 | | |
5688 | | void DrawingML::WriteShapeStyle( const Reference< XPropertySet >& xPropSet ) |
5689 | 0 | { |
5690 | | // check existence of the grab bag |
5691 | 0 | if ( !GetProperty( xPropSet, u"InteropGrabBag"_ustr ) ) |
5692 | 0 | return; |
5693 | | |
5694 | | // extract the relevant properties from the grab bag |
5695 | 0 | Sequence< PropertyValue > aGrabBag; |
5696 | 0 | Sequence< PropertyValue > aFillRefProperties, aLnRefProperties, aEffectRefProperties; |
5697 | 0 | mAny >>= aGrabBag; |
5698 | 0 | for (const auto& rProp : aGrabBag) |
5699 | 0 | { |
5700 | 0 | if( rProp.Name == "StyleFillRef" ) |
5701 | 0 | rProp.Value >>= aFillRefProperties; |
5702 | 0 | else if( rProp.Name == "StyleLnRef" ) |
5703 | 0 | rProp.Value >>= aLnRefProperties; |
5704 | 0 | else if( rProp.Name == "StyleEffectRef" ) |
5705 | 0 | rProp.Value >>= aEffectRefProperties; |
5706 | 0 | } |
5707 | |
|
5708 | 0 | WriteStyleProperties( XML_lnRef, aLnRefProperties ); |
5709 | 0 | WriteStyleProperties( XML_fillRef, aFillRefProperties ); |
5710 | 0 | WriteStyleProperties( XML_effectRef, aEffectRefProperties ); |
5711 | | |
5712 | | // write mock <a:fontRef> |
5713 | 0 | mpFS->singleElementNS(XML_a, XML_fontRef, XML_idx, "minor"); |
5714 | 0 | } |
5715 | | |
5716 | | void DrawingML::WriteShapeEffect( std::u16string_view sName, const Sequence< PropertyValue >& aEffectProps ) |
5717 | 0 | { |
5718 | 0 | if( !aEffectProps.hasElements() ) |
5719 | 0 | return; |
5720 | | |
5721 | | // assign the proper tag and enable bContainsColor if necessary |
5722 | 0 | sal_Int32 nEffectToken = 0; |
5723 | 0 | bool bContainsColor = false; |
5724 | 0 | if( sName == u"outerShdw" ) |
5725 | 0 | { |
5726 | 0 | nEffectToken = FSNS( XML_a, XML_outerShdw ); |
5727 | 0 | bContainsColor = true; |
5728 | 0 | } |
5729 | 0 | else if( sName == u"innerShdw" ) |
5730 | 0 | { |
5731 | 0 | nEffectToken = FSNS( XML_a, XML_innerShdw ); |
5732 | 0 | bContainsColor = true; |
5733 | 0 | } |
5734 | 0 | else if( sName == u"glow" || sName == u"glowtext" ) |
5735 | 0 | { |
5736 | 0 | nEffectToken = FSNS( XML_a, XML_glow ); |
5737 | 0 | bContainsColor = true; |
5738 | 0 | } |
5739 | 0 | else if( sName == u"softEdge" ) |
5740 | 0 | nEffectToken = FSNS( XML_a, XML_softEdge ); |
5741 | 0 | else if( sName == u"reflection" ) |
5742 | 0 | nEffectToken = FSNS( XML_a, XML_reflection ); |
5743 | 0 | else if( sName == u"blur" ) |
5744 | 0 | nEffectToken = FSNS( XML_a, XML_blur ); |
5745 | |
|
5746 | 0 | OUString sSchemeClr; |
5747 | 0 | ::Color nRgbClr; |
5748 | 0 | sal_Int32 nAlpha = MAX_PERCENT; |
5749 | 0 | Sequence< PropertyValue > aTransformations; |
5750 | 0 | rtl::Reference<sax_fastparser::FastAttributeList> aOuterShdwAttrList = FastSerializerHelper::createAttrList(); |
5751 | 0 | for( const auto& rEffectProp : aEffectProps ) |
5752 | 0 | { |
5753 | 0 | if( rEffectProp.Name == "Attribs" ) |
5754 | 0 | { |
5755 | | // read tag attributes |
5756 | 0 | uno::Sequence< beans::PropertyValue > aOuterShdwProps; |
5757 | 0 | rEffectProp.Value >>= aOuterShdwProps; |
5758 | 0 | for (const auto& rOuterShdwProp : aOuterShdwProps) |
5759 | 0 | { |
5760 | 0 | if( rOuterShdwProp.Name == "algn" ) |
5761 | 0 | { |
5762 | 0 | OUString sVal; |
5763 | 0 | rOuterShdwProp.Value >>= sVal; |
5764 | 0 | aOuterShdwAttrList->add( XML_algn, sVal ); |
5765 | 0 | } |
5766 | 0 | else if( rOuterShdwProp.Name == "blurRad" ) |
5767 | 0 | { |
5768 | 0 | sal_Int64 nVal = 0; |
5769 | 0 | rOuterShdwProp.Value >>= nVal; |
5770 | 0 | aOuterShdwAttrList->add( XML_blurRad, OString::number( nVal ) ); |
5771 | 0 | } |
5772 | 0 | else if( rOuterShdwProp.Name == "dir" ) |
5773 | 0 | { |
5774 | 0 | sal_Int32 nVal = 0; |
5775 | 0 | rOuterShdwProp.Value >>= nVal; |
5776 | 0 | aOuterShdwAttrList->add( XML_dir, OString::number( nVal ) ); |
5777 | 0 | } |
5778 | 0 | else if( rOuterShdwProp.Name == "dist" ) |
5779 | 0 | { |
5780 | 0 | sal_Int32 nVal = 0; |
5781 | 0 | rOuterShdwProp.Value >>= nVal; |
5782 | 0 | aOuterShdwAttrList->add( XML_dist, OString::number( nVal ) ); |
5783 | 0 | } |
5784 | 0 | else if( rOuterShdwProp.Name == "kx" ) |
5785 | 0 | { |
5786 | 0 | sal_Int32 nVal = 0; |
5787 | 0 | rOuterShdwProp.Value >>= nVal; |
5788 | 0 | aOuterShdwAttrList->add( XML_kx, OString::number( nVal ) ); |
5789 | 0 | } |
5790 | 0 | else if( rOuterShdwProp.Name == "ky" ) |
5791 | 0 | { |
5792 | 0 | sal_Int32 nVal = 0; |
5793 | 0 | rOuterShdwProp.Value >>= nVal; |
5794 | 0 | aOuterShdwAttrList->add( XML_ky, OString::number( nVal ) ); |
5795 | 0 | } |
5796 | 0 | else if( rOuterShdwProp.Name == "rotWithShape" ) |
5797 | 0 | { |
5798 | 0 | sal_Int32 nVal = 0; |
5799 | 0 | rOuterShdwProp.Value >>= nVal; |
5800 | 0 | aOuterShdwAttrList->add( XML_rotWithShape, OString::number( nVal ) ); |
5801 | 0 | } |
5802 | 0 | else if( rOuterShdwProp.Name == "sx" ) |
5803 | 0 | { |
5804 | 0 | sal_Int32 nVal = 0; |
5805 | 0 | rOuterShdwProp.Value >>= nVal; |
5806 | 0 | aOuterShdwAttrList->add( XML_sx, OString::number( nVal ) ); |
5807 | 0 | } |
5808 | 0 | else if( rOuterShdwProp.Name == "sy" ) |
5809 | 0 | { |
5810 | 0 | sal_Int32 nVal = 0; |
5811 | 0 | rOuterShdwProp.Value >>= nVal; |
5812 | 0 | aOuterShdwAttrList->add( XML_sy, OString::number( nVal ) ); |
5813 | 0 | } |
5814 | 0 | else if( rOuterShdwProp.Name == "rad" ) |
5815 | 0 | { |
5816 | 0 | sal_Int64 nVal = 0; |
5817 | 0 | rOuterShdwProp.Value >>= nVal; |
5818 | 0 | aOuterShdwAttrList->add( XML_rad, OString::number( nVal ) ); |
5819 | 0 | } |
5820 | 0 | else if( rOuterShdwProp.Name == "endA" ) |
5821 | 0 | { |
5822 | 0 | sal_Int32 nVal = 0; |
5823 | 0 | rOuterShdwProp.Value >>= nVal; |
5824 | 0 | aOuterShdwAttrList->add( XML_endA, OString::number( nVal ) ); |
5825 | 0 | } |
5826 | 0 | else if( rOuterShdwProp.Name == "endPos" ) |
5827 | 0 | { |
5828 | 0 | sal_Int32 nVal = 0; |
5829 | 0 | rOuterShdwProp.Value >>= nVal; |
5830 | 0 | aOuterShdwAttrList->add( XML_endPos, OString::number( nVal ) ); |
5831 | 0 | } |
5832 | 0 | else if( rOuterShdwProp.Name == "fadeDir" ) |
5833 | 0 | { |
5834 | 0 | sal_Int32 nVal = 0; |
5835 | 0 | rOuterShdwProp.Value >>= nVal; |
5836 | 0 | aOuterShdwAttrList->add( XML_fadeDir, OString::number( nVal ) ); |
5837 | 0 | } |
5838 | 0 | else if( rOuterShdwProp.Name == "stA" ) |
5839 | 0 | { |
5840 | 0 | sal_Int32 nVal = 0; |
5841 | 0 | rOuterShdwProp.Value >>= nVal; |
5842 | 0 | aOuterShdwAttrList->add( XML_stA, OString::number( nVal ) ); |
5843 | 0 | } |
5844 | 0 | else if( rOuterShdwProp.Name == "stPos" ) |
5845 | 0 | { |
5846 | 0 | sal_Int32 nVal = 0; |
5847 | 0 | rOuterShdwProp.Value >>= nVal; |
5848 | 0 | aOuterShdwAttrList->add( XML_stPos, OString::number( nVal ) ); |
5849 | 0 | } |
5850 | 0 | else if( rOuterShdwProp.Name == "grow" ) |
5851 | 0 | { |
5852 | 0 | sal_Int32 nVal = 0; |
5853 | 0 | rOuterShdwProp.Value >>= nVal; |
5854 | 0 | aOuterShdwAttrList->add( XML_grow, OString::number( nVal ) ); |
5855 | 0 | } |
5856 | 0 | } |
5857 | 0 | } |
5858 | 0 | else if(rEffectProp.Name == "RgbClr") |
5859 | 0 | { |
5860 | 0 | rEffectProp.Value >>= nRgbClr; |
5861 | 0 | } |
5862 | 0 | else if(rEffectProp.Name == "RgbClrTransparency") |
5863 | 0 | { |
5864 | 0 | sal_Int32 nTransparency; |
5865 | 0 | if (rEffectProp.Value >>= nTransparency) |
5866 | | // Calculate alpha value (see oox/source/drawingml/color.cxx : getTransparency()) |
5867 | 0 | nAlpha = MAX_PERCENT - ( PER_PERCENT * nTransparency ); |
5868 | 0 | } |
5869 | 0 | else if(rEffectProp.Name == "SchemeClr") |
5870 | 0 | { |
5871 | 0 | rEffectProp.Value >>= sSchemeClr; |
5872 | 0 | } |
5873 | 0 | else if(rEffectProp.Name == "SchemeClrTransformations") |
5874 | 0 | { |
5875 | 0 | rEffectProp.Value >>= aTransformations; |
5876 | 0 | } |
5877 | 0 | } |
5878 | |
|
5879 | 0 | if( nEffectToken <= 0 ) |
5880 | 0 | return; |
5881 | | |
5882 | 0 | mpFS->startElement( nEffectToken, aOuterShdwAttrList ); |
5883 | |
|
5884 | 0 | if( bContainsColor ) |
5885 | 0 | { |
5886 | 0 | if( sSchemeClr.isEmpty() ) |
5887 | 0 | WriteColor( nRgbClr, nAlpha ); |
5888 | 0 | else |
5889 | 0 | WriteColor( sSchemeClr, aTransformations ); |
5890 | 0 | } |
5891 | |
|
5892 | 0 | mpFS->endElement( nEffectToken ); |
5893 | 0 | } |
5894 | | |
5895 | | static sal_Int32 lcl_CalculateDist(const double dX, const double dY) |
5896 | 0 | { |
5897 | 0 | return static_cast< sal_Int32 >(std::hypot(dX, dY) * 360); |
5898 | 0 | } |
5899 | | |
5900 | | static sal_Int32 lcl_CalculateDir(const double dX, const double dY) |
5901 | 0 | { |
5902 | 0 | return (static_cast< sal_Int32 >(basegfx::rad2deg<60000>(atan2(dY,dX))) + 21600000) % 21600000; |
5903 | 0 | } |
5904 | | |
5905 | | void DrawingML::WriteShapeEffects( const Reference< XPropertySet >& rXPropSet ) |
5906 | 0 | { |
5907 | 0 | Sequence< PropertyValue > aGrabBag, aEffects, aOuterShdwProps; |
5908 | 0 | bool bHasInteropGrabBag = rXPropSet->getPropertySetInfo()->hasPropertyByName(u"InteropGrabBag"_ustr); |
5909 | 0 | if (bHasInteropGrabBag && GetProperty(rXPropSet, u"InteropGrabBag"_ustr)) |
5910 | 0 | { |
5911 | 0 | mAny >>= aGrabBag; |
5912 | 0 | auto pProp = std::find_if(std::cbegin(aGrabBag), std::cend(aGrabBag), |
5913 | 0 | [](const PropertyValue& rProp) { return rProp.Name == "EffectProperties"; }); |
5914 | 0 | if (pProp != std::cend(aGrabBag)) |
5915 | 0 | { |
5916 | 0 | pProp->Value >>= aEffects; |
5917 | 0 | auto pEffect = std::find_if(std::cbegin(aEffects), std::cend(aEffects), |
5918 | 0 | [](const PropertyValue& rEffect) { return rEffect.Name == "outerShdw"; }); |
5919 | 0 | if (pEffect != std::cend(aEffects)) |
5920 | 0 | pEffect->Value >>= aOuterShdwProps; |
5921 | 0 | } |
5922 | 0 | } |
5923 | | |
5924 | | // tdf#132201: the order of effects is important. Effects order (CT_EffectList in ECMA-376): |
5925 | | // blur -> fillOverlay -> glow -> innerShdw -> outerShdw -> prstShdw -> reflection -> softEdge |
5926 | |
|
5927 | 0 | if( !aEffects.hasElements() ) |
5928 | 0 | { |
5929 | 0 | bool bHasShadow = false; |
5930 | 0 | if( GetProperty( rXPropSet, u"Shadow"_ustr ) ) |
5931 | 0 | mAny >>= bHasShadow; |
5932 | 0 | bool bHasEffects = bHasShadow; |
5933 | 0 | if (!bHasEffects && GetProperty(rXPropSet, u"GlowEffectRadius"_ustr)) |
5934 | 0 | { |
5935 | 0 | sal_Int32 rad = 0; |
5936 | 0 | mAny >>= rad; |
5937 | 0 | bHasEffects = rad > 0; |
5938 | 0 | } |
5939 | 0 | if (!bHasEffects && GetProperty(rXPropSet, u"SoftEdgeRadius"_ustr)) |
5940 | 0 | { |
5941 | 0 | sal_Int32 rad = 0; |
5942 | 0 | mAny >>= rad; |
5943 | 0 | bHasEffects = rad > 0; |
5944 | 0 | } |
5945 | |
|
5946 | 0 | if (bHasEffects) |
5947 | 0 | { |
5948 | 0 | mpFS->startElementNS(XML_a, XML_effectLst); |
5949 | 0 | WriteGlowEffect(rXPropSet); |
5950 | 0 | if( bHasShadow ) |
5951 | 0 | { |
5952 | 0 | double dX = +0.0, dY = +0.0; |
5953 | 0 | sal_Int32 nBlur =0; |
5954 | 0 | rXPropSet->getPropertyValue( u"ShadowXDistance"_ustr ) >>= dX; |
5955 | 0 | rXPropSet->getPropertyValue( u"ShadowYDistance"_ustr ) >>= dY; |
5956 | 0 | rXPropSet->getPropertyValue( u"ShadowBlur"_ustr ) >>= nBlur; |
5957 | |
|
5958 | 0 | Sequence< PropertyValue > aShadowAttribsGrabBag{ |
5959 | 0 | comphelper::makePropertyValue(u"dist"_ustr, lcl_CalculateDist(dX, dY)), |
5960 | 0 | comphelper::makePropertyValue(u"dir"_ustr, lcl_CalculateDir(dX, dY)), |
5961 | 0 | comphelper::makePropertyValue(u"blurRad"_ustr, oox::drawingml::convertHmmToEmu(nBlur)), |
5962 | 0 | comphelper::makePropertyValue(u"rotWithShape"_ustr, false) //ooxml default is 'true', so must write it |
5963 | 0 | }; |
5964 | |
|
5965 | 0 | Sequence< PropertyValue > aShadowGrabBag{ |
5966 | 0 | comphelper::makePropertyValue(u"Attribs"_ustr, aShadowAttribsGrabBag), |
5967 | 0 | comphelper::makePropertyValue(u"RgbClr"_ustr, rXPropSet->getPropertyValue( u"ShadowColor"_ustr )), |
5968 | 0 | comphelper::makePropertyValue(u"RgbClrTransparency"_ustr, rXPropSet->getPropertyValue( u"ShadowTransparence"_ustr )) |
5969 | 0 | }; |
5970 | |
|
5971 | 0 | WriteShapeEffect( u"outerShdw", aShadowGrabBag ); |
5972 | 0 | } |
5973 | 0 | WriteSoftEdgeEffect(rXPropSet); |
5974 | 0 | mpFS->endElementNS(XML_a, XML_effectLst); |
5975 | 0 | } |
5976 | 0 | } |
5977 | 0 | else |
5978 | 0 | { |
5979 | 0 | for( auto& rOuterShdwProp : asNonConstRange(aOuterShdwProps) ) |
5980 | 0 | { |
5981 | 0 | if( rOuterShdwProp.Name == "Attribs" ) |
5982 | 0 | { |
5983 | 0 | Sequence< PropertyValue > aAttribsProps; |
5984 | 0 | rOuterShdwProp.Value >>= aAttribsProps; |
5985 | |
|
5986 | 0 | double dX = +0.0, dY = +0.0; |
5987 | 0 | sal_Int32 nBlur =0; |
5988 | 0 | rXPropSet->getPropertyValue( u"ShadowXDistance"_ustr ) >>= dX; |
5989 | 0 | rXPropSet->getPropertyValue( u"ShadowYDistance"_ustr ) >>= dY; |
5990 | 0 | rXPropSet->getPropertyValue( u"ShadowBlur"_ustr ) >>= nBlur; |
5991 | | |
5992 | |
|
5993 | 0 | for( auto& rAttribsProp : asNonConstRange(aAttribsProps) ) |
5994 | 0 | { |
5995 | 0 | if( rAttribsProp.Name == "dist" ) |
5996 | 0 | { |
5997 | 0 | rAttribsProp.Value <<= lcl_CalculateDist(dX, dY); |
5998 | 0 | } |
5999 | 0 | else if( rAttribsProp.Name == "dir" ) |
6000 | 0 | { |
6001 | 0 | rAttribsProp.Value <<= lcl_CalculateDir(dX, dY); |
6002 | 0 | } |
6003 | 0 | else if( rAttribsProp.Name == "blurRad" ) |
6004 | 0 | { |
6005 | 0 | rAttribsProp.Value <<= oox::drawingml::convertHmmToEmu(nBlur); |
6006 | 0 | } |
6007 | 0 | } |
6008 | |
|
6009 | 0 | rOuterShdwProp.Value <<= aAttribsProps; |
6010 | 0 | } |
6011 | 0 | else if( rOuterShdwProp.Name == "RgbClr" ) |
6012 | 0 | { |
6013 | 0 | rOuterShdwProp.Value = rXPropSet->getPropertyValue( u"ShadowColor"_ustr ); |
6014 | 0 | } |
6015 | 0 | else if( rOuterShdwProp.Name == "RgbClrTransparency" ) |
6016 | 0 | { |
6017 | 0 | rOuterShdwProp.Value = rXPropSet->getPropertyValue( u"ShadowTransparence"_ustr ); |
6018 | 0 | } |
6019 | 0 | } |
6020 | |
|
6021 | 0 | mpFS->startElementNS(XML_a, XML_effectLst); |
6022 | 0 | bool bGlowWritten = false; |
6023 | 0 | for (const auto& rEffect : aEffects) |
6024 | 0 | { |
6025 | 0 | if (!bGlowWritten |
6026 | 0 | && (rEffect.Name == "innerShdw" || rEffect.Name == "outerShdw" |
6027 | 0 | || rEffect.Name == "prstShdw" || rEffect.Name == "reflection" |
6028 | 0 | || rEffect.Name == "softEdge")) |
6029 | 0 | { |
6030 | 0 | WriteGlowEffect(rXPropSet); |
6031 | 0 | bGlowWritten = true; |
6032 | 0 | } |
6033 | |
|
6034 | 0 | if( rEffect.Name == "outerShdw" ) |
6035 | 0 | { |
6036 | 0 | WriteShapeEffect( rEffect.Name, aOuterShdwProps ); |
6037 | 0 | } |
6038 | 0 | else |
6039 | 0 | { |
6040 | 0 | Sequence< PropertyValue > aEffectProps; |
6041 | 0 | rEffect.Value >>= aEffectProps; |
6042 | 0 | WriteShapeEffect( rEffect.Name, aEffectProps ); |
6043 | 0 | } |
6044 | 0 | } |
6045 | 0 | if (!bGlowWritten) |
6046 | 0 | WriteGlowEffect(rXPropSet); |
6047 | 0 | WriteSoftEdgeEffect(rXPropSet); // the last |
6048 | |
|
6049 | 0 | mpFS->endElementNS(XML_a, XML_effectLst); |
6050 | 0 | } |
6051 | 0 | } |
6052 | | |
6053 | | void DrawingML::WriteGlowEffect(const Reference< XPropertySet >& rXPropSet) |
6054 | 0 | { |
6055 | 0 | if (!rXPropSet->getPropertySetInfo()->hasPropertyByName(u"GlowEffectRadius"_ustr)) |
6056 | 0 | { |
6057 | 0 | return; |
6058 | 0 | } |
6059 | | |
6060 | 0 | sal_Int32 nRad = 0; |
6061 | 0 | rXPropSet->getPropertyValue(u"GlowEffectRadius"_ustr) >>= nRad; |
6062 | 0 | if (!nRad) |
6063 | 0 | return; |
6064 | | |
6065 | 0 | Sequence< PropertyValue > aGlowAttribs{ comphelper::makePropertyValue( |
6066 | 0 | u"rad"_ustr, oox::drawingml::convertHmmToEmu(nRad)) }; |
6067 | 0 | Sequence< PropertyValue > aGlowProps{ |
6068 | 0 | comphelper::makePropertyValue(u"Attribs"_ustr, aGlowAttribs), |
6069 | 0 | comphelper::makePropertyValue(u"RgbClr"_ustr, rXPropSet->getPropertyValue(u"GlowEffectColor"_ustr)), |
6070 | 0 | comphelper::makePropertyValue(u"RgbClrTransparency"_ustr, rXPropSet->getPropertyValue(u"GlowEffectTransparency"_ustr)) |
6071 | 0 | }; |
6072 | | // TODO other stuff like saturation or luminance |
6073 | |
|
6074 | 0 | WriteShapeEffect(u"glow", aGlowProps); |
6075 | 0 | } |
6076 | | |
6077 | | void DrawingML::WriteTextGlowEffect(const Reference< XPropertySet >& rXPropSet) |
6078 | 0 | { |
6079 | 0 | if (!rXPropSet->getPropertySetInfo()->hasPropertyByName(u"GlowTextEffectRadius"_ustr)) |
6080 | 0 | { |
6081 | 0 | return; |
6082 | 0 | } |
6083 | | |
6084 | 0 | sal_Int32 nRad = 0; |
6085 | 0 | rXPropSet->getPropertyValue(u"GlowTextEffectRadius"_ustr) >>= nRad; |
6086 | 0 | if (!nRad) |
6087 | 0 | return; |
6088 | | |
6089 | 0 | Sequence< PropertyValue > aGlowAttribs{ comphelper::makePropertyValue( |
6090 | 0 | u"rad"_ustr, oox::drawingml::convertHmmToEmu(nRad)) }; |
6091 | 0 | Sequence< PropertyValue > aGlowProps{ |
6092 | 0 | comphelper::makePropertyValue(u"Attribs"_ustr, aGlowAttribs), |
6093 | 0 | comphelper::makePropertyValue(u"RgbClr"_ustr, rXPropSet->getPropertyValue(u"GlowTextEffectColor"_ustr)), |
6094 | 0 | comphelper::makePropertyValue(u"RgbClrTransparency"_ustr, rXPropSet->getPropertyValue(u"GlowTextEffectTransparency"_ustr)) |
6095 | 0 | }; |
6096 | | // TODO other stuff like saturation or luminance |
6097 | |
|
6098 | 0 | WriteShapeEffect(u"glowtext", aGlowProps); |
6099 | 0 | } |
6100 | | |
6101 | | void DrawingML::WriteSoftEdgeEffect(const css::uno::Reference<css::beans::XPropertySet>& rXPropSet) |
6102 | 0 | { |
6103 | 0 | if (!rXPropSet->getPropertySetInfo()->hasPropertyByName(u"SoftEdgeRadius"_ustr)) |
6104 | 0 | { |
6105 | 0 | return; |
6106 | 0 | } |
6107 | | |
6108 | 0 | sal_Int32 nRad = 0; |
6109 | 0 | rXPropSet->getPropertyValue(u"SoftEdgeRadius"_ustr) >>= nRad; |
6110 | 0 | if (!nRad) |
6111 | 0 | return; |
6112 | | |
6113 | 0 | css::uno::Sequence<css::beans::PropertyValue> aAttribs{ comphelper::makePropertyValue( |
6114 | 0 | u"rad"_ustr, oox::drawingml::convertHmmToEmu(nRad)) }; |
6115 | 0 | css::uno::Sequence<css::beans::PropertyValue> aProps{ comphelper::makePropertyValue(u"Attribs"_ustr, |
6116 | 0 | aAttribs) }; |
6117 | |
|
6118 | 0 | WriteShapeEffect(u"softEdge", aProps); |
6119 | 0 | } |
6120 | | |
6121 | | void DrawingML::Write3DEffects( const Reference< XPropertySet >& xPropSet, bool bIsText ) |
6122 | 0 | { |
6123 | | // check existence of the grab bag |
6124 | 0 | if( !GetProperty( xPropSet, u"InteropGrabBag"_ustr ) ) |
6125 | 0 | return; |
6126 | | |
6127 | | // extract the relevant properties from the grab bag |
6128 | 0 | Sequence< PropertyValue > aGrabBag, aEffectProps, aLightRigProps, aShape3DProps; |
6129 | 0 | mAny >>= aGrabBag; |
6130 | |
|
6131 | 0 | auto pShapeProp = std::find_if( std::cbegin(aGrabBag), std::cend(aGrabBag), |
6132 | 0 | [bIsText](const PropertyValue& rProp) |
6133 | 0 | { return rProp.Name == (bIsText ? u"Text3DEffectProperties" : u"3DEffectProperties"); }); |
6134 | 0 | if (pShapeProp != std::cend(aGrabBag)) |
6135 | 0 | { |
6136 | 0 | Sequence< PropertyValue > a3DEffectProps; |
6137 | 0 | pShapeProp->Value >>= a3DEffectProps; |
6138 | 0 | for (const auto& r3DEffectProp : a3DEffectProps) |
6139 | 0 | { |
6140 | 0 | if( r3DEffectProp.Name == "Camera" ) |
6141 | 0 | r3DEffectProp.Value >>= aEffectProps; |
6142 | 0 | else if( r3DEffectProp.Name == "LightRig" ) |
6143 | 0 | r3DEffectProp.Value >>= aLightRigProps; |
6144 | 0 | else if( r3DEffectProp.Name == "Shape3D" ) |
6145 | 0 | r3DEffectProp.Value >>= aShape3DProps; |
6146 | 0 | } |
6147 | 0 | } |
6148 | |
|
6149 | 0 | if( !aEffectProps.hasElements() && !aLightRigProps.hasElements() && !aShape3DProps.hasElements() ) |
6150 | 0 | return; |
6151 | | |
6152 | 0 | bool bCameraRotationPresent = false; |
6153 | 0 | rtl::Reference<sax_fastparser::FastAttributeList> aCameraAttrList = FastSerializerHelper::createAttrList(); |
6154 | 0 | rtl::Reference<sax_fastparser::FastAttributeList> aCameraRotationAttrList = FastSerializerHelper::createAttrList(); |
6155 | 0 | for (const auto& rEffectProp : aEffectProps) |
6156 | 0 | { |
6157 | 0 | if( rEffectProp.Name == "prst" ) |
6158 | 0 | { |
6159 | 0 | OUString sVal; |
6160 | 0 | rEffectProp.Value >>= sVal; |
6161 | 0 | aCameraAttrList->add(XML_prst, sVal); |
6162 | 0 | } |
6163 | 0 | else if( rEffectProp.Name == "fov" ) |
6164 | 0 | { |
6165 | 0 | float fVal = 0; |
6166 | 0 | rEffectProp.Value >>= fVal; |
6167 | 0 | aCameraAttrList->add( XML_fov, OString::number( fVal * 60000 ) ); |
6168 | 0 | } |
6169 | 0 | else if( rEffectProp.Name == "zoom" ) |
6170 | 0 | { |
6171 | 0 | float fVal = 1; |
6172 | 0 | rEffectProp.Value >>= fVal; |
6173 | 0 | aCameraAttrList->add( XML_zoom, OString::number( fVal * 100000 ) ); |
6174 | 0 | } |
6175 | 0 | else if( rEffectProp.Name == "rotLat" || |
6176 | 0 | rEffectProp.Name == "rotLon" || |
6177 | 0 | rEffectProp.Name == "rotRev" ) |
6178 | 0 | { |
6179 | 0 | sal_Int32 nVal = 0, nToken = XML_none; |
6180 | 0 | rEffectProp.Value >>= nVal; |
6181 | 0 | if( rEffectProp.Name == "rotLat" ) |
6182 | 0 | nToken = XML_lat; |
6183 | 0 | else if( rEffectProp.Name == "rotLon" ) |
6184 | 0 | nToken = XML_lon; |
6185 | 0 | else if( rEffectProp.Name == "rotRev" ) |
6186 | 0 | nToken = XML_rev; |
6187 | 0 | aCameraRotationAttrList->add( nToken, OString::number( nVal ) ); |
6188 | 0 | bCameraRotationPresent = true; |
6189 | 0 | } |
6190 | 0 | } |
6191 | |
|
6192 | 0 | bool bLightRigRotationPresent = false; |
6193 | 0 | rtl::Reference<sax_fastparser::FastAttributeList> aLightRigAttrList = FastSerializerHelper::createAttrList(); |
6194 | 0 | rtl::Reference<sax_fastparser::FastAttributeList> aLightRigRotationAttrList = FastSerializerHelper::createAttrList(); |
6195 | 0 | for (const auto& rLightRigProp : aLightRigProps) |
6196 | 0 | { |
6197 | 0 | if( rLightRigProp.Name == "rig" || rLightRigProp.Name == "dir" ) |
6198 | 0 | { |
6199 | 0 | OUString sVal; |
6200 | 0 | sal_Int32 nToken = XML_none; |
6201 | 0 | rLightRigProp.Value >>= sVal; |
6202 | 0 | if( rLightRigProp.Name == "rig" ) |
6203 | 0 | nToken = XML_rig; |
6204 | 0 | else if( rLightRigProp.Name == "dir" ) |
6205 | 0 | nToken = XML_dir; |
6206 | 0 | aLightRigAttrList->add(nToken, sVal); |
6207 | 0 | } |
6208 | 0 | else if( rLightRigProp.Name == "rotLat" || |
6209 | 0 | rLightRigProp.Name == "rotLon" || |
6210 | 0 | rLightRigProp.Name == "rotRev" ) |
6211 | 0 | { |
6212 | 0 | sal_Int32 nVal = 0, nToken = XML_none; |
6213 | 0 | rLightRigProp.Value >>= nVal; |
6214 | 0 | if( rLightRigProp.Name == "rotLat" ) |
6215 | 0 | nToken = XML_lat; |
6216 | 0 | else if( rLightRigProp.Name == "rotLon" ) |
6217 | 0 | nToken = XML_lon; |
6218 | 0 | else if( rLightRigProp.Name == "rotRev" ) |
6219 | 0 | nToken = XML_rev; |
6220 | 0 | aLightRigRotationAttrList->add( nToken, OString::number( nVal ) ); |
6221 | 0 | bLightRigRotationPresent = true; |
6222 | 0 | } |
6223 | 0 | } |
6224 | |
|
6225 | 0 | mpFS->startElementNS(XML_a, XML_scene3d); |
6226 | |
|
6227 | 0 | if( aEffectProps.hasElements() ) |
6228 | 0 | { |
6229 | 0 | mpFS->startElementNS( XML_a, XML_camera, aCameraAttrList ); |
6230 | 0 | if( bCameraRotationPresent ) |
6231 | 0 | { |
6232 | 0 | mpFS->singleElementNS( XML_a, XML_rot, aCameraRotationAttrList ); |
6233 | 0 | } |
6234 | 0 | mpFS->endElementNS( XML_a, XML_camera ); |
6235 | 0 | } |
6236 | 0 | else |
6237 | 0 | { |
6238 | | // a:camera with Word default values - Word won't open the document if this is not present |
6239 | 0 | mpFS->singleElementNS(XML_a, XML_camera, XML_prst, "orthographicFront"); |
6240 | 0 | } |
6241 | |
|
6242 | 0 | if( aEffectProps.hasElements() ) |
6243 | 0 | { |
6244 | 0 | mpFS->startElementNS( XML_a, XML_lightRig, aLightRigAttrList ); |
6245 | 0 | if( bLightRigRotationPresent ) |
6246 | 0 | { |
6247 | 0 | mpFS->singleElementNS( XML_a, XML_rot, aLightRigRotationAttrList ); |
6248 | 0 | } |
6249 | 0 | mpFS->endElementNS( XML_a, XML_lightRig ); |
6250 | 0 | } |
6251 | 0 | else |
6252 | 0 | { |
6253 | | // a:lightRig with Word default values - Word won't open the document if this is not present |
6254 | 0 | mpFS->singleElementNS(XML_a, XML_lightRig, XML_rig, "threePt", XML_dir, "t"); |
6255 | 0 | } |
6256 | |
|
6257 | 0 | mpFS->endElementNS( XML_a, XML_scene3d ); |
6258 | |
|
6259 | 0 | if( !aShape3DProps.hasElements() ) |
6260 | 0 | return; |
6261 | | |
6262 | 0 | bool bBevelTPresent = false, bBevelBPresent = false; |
6263 | 0 | Sequence< PropertyValue > aExtrusionColorProps, aContourColorProps; |
6264 | 0 | rtl::Reference<sax_fastparser::FastAttributeList> aBevelTAttrList = FastSerializerHelper::createAttrList(); |
6265 | 0 | rtl::Reference<sax_fastparser::FastAttributeList> aBevelBAttrList = FastSerializerHelper::createAttrList(); |
6266 | 0 | rtl::Reference<sax_fastparser::FastAttributeList> aShape3DAttrList = FastSerializerHelper::createAttrList(); |
6267 | 0 | for (const auto& rShape3DProp : aShape3DProps) |
6268 | 0 | { |
6269 | 0 | if( rShape3DProp.Name == "extrusionH" || rShape3DProp.Name == "contourW" || rShape3DProp.Name == "z" ) |
6270 | 0 | { |
6271 | 0 | sal_Int32 nVal = 0, nToken = XML_none; |
6272 | 0 | rShape3DProp.Value >>= nVal; |
6273 | 0 | if( rShape3DProp.Name == "extrusionH" ) |
6274 | 0 | nToken = XML_extrusionH; |
6275 | 0 | else if( rShape3DProp.Name == "contourW" ) |
6276 | 0 | nToken = XML_contourW; |
6277 | 0 | else if( rShape3DProp.Name == "z" ) |
6278 | 0 | nToken = XML_z; |
6279 | 0 | aShape3DAttrList->add( nToken, OString::number( nVal ) ); |
6280 | 0 | } |
6281 | 0 | else if( rShape3DProp.Name == "prstMaterial" ) |
6282 | 0 | { |
6283 | 0 | OUString sVal; |
6284 | 0 | rShape3DProp.Value >>= sVal; |
6285 | 0 | aShape3DAttrList->add(XML_prstMaterial, sVal); |
6286 | 0 | } |
6287 | 0 | else if( rShape3DProp.Name == "extrusionClr" ) |
6288 | 0 | { |
6289 | 0 | rShape3DProp.Value >>= aExtrusionColorProps; |
6290 | 0 | } |
6291 | 0 | else if( rShape3DProp.Name == "contourClr" ) |
6292 | 0 | { |
6293 | 0 | rShape3DProp.Value >>= aContourColorProps; |
6294 | 0 | } |
6295 | 0 | else if( rShape3DProp.Name == "bevelT" || rShape3DProp.Name == "bevelB" ) |
6296 | 0 | { |
6297 | 0 | Sequence< PropertyValue > aBevelProps; |
6298 | 0 | rShape3DProp.Value >>= aBevelProps; |
6299 | 0 | if ( !aBevelProps.hasElements() ) |
6300 | 0 | continue; |
6301 | | |
6302 | 0 | rtl::Reference<sax_fastparser::FastAttributeList> aBevelAttrList; |
6303 | 0 | if( rShape3DProp.Name == "bevelT" ) |
6304 | 0 | { |
6305 | 0 | bBevelTPresent = true; |
6306 | 0 | aBevelAttrList = aBevelTAttrList; |
6307 | 0 | } |
6308 | 0 | else |
6309 | 0 | { |
6310 | 0 | bBevelBPresent = true; |
6311 | 0 | aBevelAttrList = aBevelBAttrList; |
6312 | 0 | } |
6313 | 0 | for (const auto& rBevelProp : aBevelProps) |
6314 | 0 | { |
6315 | 0 | if( rBevelProp.Name == "w" || rBevelProp.Name == "h" ) |
6316 | 0 | { |
6317 | 0 | sal_Int32 nVal = 0, nToken = XML_none; |
6318 | 0 | rBevelProp.Value >>= nVal; |
6319 | 0 | if( rBevelProp.Name == "w" ) |
6320 | 0 | nToken = XML_w; |
6321 | 0 | else if( rBevelProp.Name == "h" ) |
6322 | 0 | nToken = XML_h; |
6323 | 0 | aBevelAttrList->add( nToken, OString::number( nVal ) ); |
6324 | 0 | } |
6325 | 0 | else if( rBevelProp.Name == "prst" ) |
6326 | 0 | { |
6327 | 0 | OUString sVal; |
6328 | 0 | rBevelProp.Value >>= sVal; |
6329 | 0 | aBevelAttrList->add(XML_prst, sVal); |
6330 | 0 | } |
6331 | 0 | } |
6332 | |
|
6333 | 0 | } |
6334 | 0 | } |
6335 | |
|
6336 | 0 | mpFS->startElementNS( XML_a, XML_sp3d, aShape3DAttrList ); |
6337 | 0 | if( bBevelTPresent ) |
6338 | 0 | { |
6339 | 0 | mpFS->singleElementNS( XML_a, XML_bevelT, aBevelTAttrList ); |
6340 | 0 | } |
6341 | 0 | if( bBevelBPresent ) |
6342 | 0 | { |
6343 | 0 | mpFS->singleElementNS( XML_a, XML_bevelB, aBevelBAttrList ); |
6344 | 0 | } |
6345 | 0 | if( aExtrusionColorProps.hasElements() ) |
6346 | 0 | { |
6347 | 0 | OUString sSchemeClr; |
6348 | 0 | ::Color nColor; |
6349 | 0 | sal_Int32 nTransparency(0); |
6350 | 0 | Sequence< PropertyValue > aColorTransformations; |
6351 | 0 | for (const auto& rExtrusionColorProp : aExtrusionColorProps) |
6352 | 0 | { |
6353 | 0 | if( rExtrusionColorProp.Name == "schemeClr" ) |
6354 | 0 | rExtrusionColorProp.Value >>= sSchemeClr; |
6355 | 0 | else if( rExtrusionColorProp.Name == "schemeClrTransformations" ) |
6356 | 0 | rExtrusionColorProp.Value >>= aColorTransformations; |
6357 | 0 | else if( rExtrusionColorProp.Name == "rgbClr" ) |
6358 | 0 | rExtrusionColorProp.Value >>= nColor; |
6359 | 0 | else if( rExtrusionColorProp.Name == "rgbClrTransparency" ) |
6360 | 0 | rExtrusionColorProp.Value >>= nTransparency; |
6361 | 0 | } |
6362 | 0 | mpFS->startElementNS(XML_a, XML_extrusionClr); |
6363 | |
|
6364 | 0 | if( sSchemeClr.isEmpty() ) |
6365 | 0 | WriteColor( nColor, MAX_PERCENT - ( PER_PERCENT * nTransparency ) ); |
6366 | 0 | else |
6367 | 0 | WriteColor( sSchemeClr, aColorTransformations ); |
6368 | |
|
6369 | 0 | mpFS->endElementNS( XML_a, XML_extrusionClr ); |
6370 | 0 | } |
6371 | 0 | if( aContourColorProps.hasElements() ) |
6372 | 0 | { |
6373 | 0 | OUString sSchemeClr; |
6374 | 0 | ::Color nColor; |
6375 | 0 | sal_Int32 nTransparency(0); |
6376 | 0 | Sequence< PropertyValue > aColorTransformations; |
6377 | 0 | for (const auto& rContourColorProp : aContourColorProps) |
6378 | 0 | { |
6379 | 0 | if( rContourColorProp.Name == "schemeClr" ) |
6380 | 0 | rContourColorProp.Value >>= sSchemeClr; |
6381 | 0 | else if( rContourColorProp.Name == "schemeClrTransformations" ) |
6382 | 0 | rContourColorProp.Value >>= aColorTransformations; |
6383 | 0 | else if( rContourColorProp.Name == "rgbClr" ) |
6384 | 0 | rContourColorProp.Value >>= nColor; |
6385 | 0 | else if( rContourColorProp.Name == "rgbClrTransparency" ) |
6386 | 0 | rContourColorProp.Value >>= nTransparency; |
6387 | 0 | } |
6388 | 0 | mpFS->startElementNS(XML_a, XML_contourClr); |
6389 | |
|
6390 | 0 | if( sSchemeClr.isEmpty() ) |
6391 | 0 | WriteColor( nColor, MAX_PERCENT - ( PER_PERCENT * nTransparency ) ); |
6392 | 0 | else |
6393 | 0 | WriteColor( sSchemeClr, aContourColorProps ); |
6394 | |
|
6395 | 0 | mpFS->endElementNS( XML_a, XML_contourClr ); |
6396 | 0 | } |
6397 | 0 | mpFS->endElementNS( XML_a, XML_sp3d ); |
6398 | 0 | } |
6399 | | |
6400 | | void DrawingML::WriteArtisticEffect( const Reference< XPropertySet >& rXPropSet ) |
6401 | 0 | { |
6402 | 0 | if( !GetProperty( rXPropSet, u"InteropGrabBag"_ustr ) ) |
6403 | 0 | return; |
6404 | | |
6405 | 0 | PropertyValue aEffect; |
6406 | 0 | Sequence< PropertyValue > aGrabBag; |
6407 | 0 | mAny >>= aGrabBag; |
6408 | 0 | auto pProp = std::find_if(std::cbegin(aGrabBag), std::cend(aGrabBag), |
6409 | 0 | [](const PropertyValue& rProp) { return rProp.Name == "ArtisticEffectProperties"; }); |
6410 | 0 | if (pProp != std::cend(aGrabBag)) |
6411 | 0 | pProp->Value >>= aEffect; |
6412 | 0 | sal_Int32 nEffectToken = ArtisticEffectProperties::getEffectToken( aEffect.Name ); |
6413 | 0 | if( nEffectToken == XML_none ) |
6414 | 0 | return; |
6415 | | |
6416 | 0 | Sequence< PropertyValue > aAttrs; |
6417 | 0 | aEffect.Value >>= aAttrs; |
6418 | 0 | rtl::Reference<sax_fastparser::FastAttributeList> aAttrList = FastSerializerHelper::createAttrList(); |
6419 | 0 | OString sRelId; |
6420 | 0 | for (const auto& rAttr : aAttrs) |
6421 | 0 | { |
6422 | 0 | sal_Int32 nToken = ArtisticEffectProperties::getEffectToken( rAttr.Name ); |
6423 | 0 | if( nToken != XML_none ) |
6424 | 0 | { |
6425 | 0 | sal_Int32 nVal = 0; |
6426 | 0 | rAttr.Value >>= nVal; |
6427 | 0 | aAttrList->add( nToken, OString::number( nVal ) ); |
6428 | 0 | } |
6429 | 0 | else if( rAttr.Name == "OriginalGraphic" ) |
6430 | 0 | { |
6431 | 0 | Sequence< PropertyValue > aGraphic; |
6432 | 0 | rAttr.Value >>= aGraphic; |
6433 | 0 | Sequence< sal_Int8 > aGraphicData; |
6434 | 0 | OUString sGraphicId; |
6435 | 0 | for (const auto& rProp : aGraphic) |
6436 | 0 | { |
6437 | 0 | if( rProp.Name == "Id" ) |
6438 | 0 | rProp.Value >>= sGraphicId; |
6439 | 0 | else if( rProp.Name == "Data" ) |
6440 | 0 | rProp.Value >>= aGraphicData; |
6441 | 0 | } |
6442 | 0 | sRelId = WriteWdpPicture( sGraphicId, aGraphicData ); |
6443 | 0 | } |
6444 | 0 | } |
6445 | |
|
6446 | 0 | mpFS->startElementNS(XML_a, XML_extLst); |
6447 | 0 | mpFS->startElementNS(XML_a, XML_ext, XML_uri, "{BEBA8EAE-BF5A-486C-A8C5-ECC9F3942E4B}"); |
6448 | 0 | mpFS->startElementNS( XML_a14, XML_imgProps, |
6449 | 0 | FSNS(XML_xmlns, XML_a14), mpFB->getNamespaceURL(OOX_NS(a14)) ); |
6450 | 0 | mpFS->startElementNS(XML_a14, XML_imgLayer, FSNS(XML_r, XML_embed), sRelId); |
6451 | 0 | mpFS->startElementNS(XML_a14, XML_imgEffect); |
6452 | |
|
6453 | 0 | mpFS->singleElementNS( XML_a14, nEffectToken, aAttrList ); |
6454 | |
|
6455 | 0 | mpFS->endElementNS( XML_a14, XML_imgEffect ); |
6456 | 0 | mpFS->endElementNS( XML_a14, XML_imgLayer ); |
6457 | 0 | mpFS->endElementNS( XML_a14, XML_imgProps ); |
6458 | 0 | mpFS->endElementNS( XML_a, XML_ext ); |
6459 | 0 | mpFS->endElementNS( XML_a, XML_extLst ); |
6460 | 0 | } |
6461 | | |
6462 | | OString DrawingML::WriteWdpPicture( const OUString& rFileId, const Sequence< sal_Int8 >& rPictureData ) |
6463 | 0 | { |
6464 | 0 | auto& rGraphicExportCache = GraphicExportCache::get(); |
6465 | |
|
6466 | 0 | OUString aId = rGraphicExportCache.findWdpID(rFileId); |
6467 | 0 | if (!aId.isEmpty()) |
6468 | 0 | return OUStringToOString(aId, RTL_TEXTENCODING_UTF8); |
6469 | | |
6470 | 0 | sal_Int32 nWdpImageCount = rGraphicExportCache.nextWdpImageCount(); |
6471 | 0 | OUString sFileName = u"media/hdphoto"_ustr + OUString::number(nWdpImageCount) + u".wdp"_ustr; |
6472 | 0 | OUString sFragment = GetComponentDir() + u"/"_ustr + sFileName; |
6473 | 0 | Reference< XOutputStream > xOutStream = mpFB->openFragmentStream(sFragment, u"image/vnd.ms-photo"_ustr); |
6474 | 0 | xOutStream->writeBytes( rPictureData ); |
6475 | 0 | xOutStream->closeOutput(); |
6476 | |
|
6477 | 0 | aId = mpFB->addRelation(mpFS->getOutputStream(), |
6478 | 0 | oox::getRelationship(Relationship::HDPHOTO), |
6479 | 0 | Concat2View(GetRelationCompPrefix() + sFileName)); |
6480 | |
|
6481 | 0 | rGraphicExportCache.addToWdpCache(rFileId, aId); |
6482 | |
|
6483 | 0 | return OUStringToOString(aId, RTL_TEXTENCODING_UTF8); |
6484 | 0 | } |
6485 | | |
6486 | | void DrawingML::WriteDiagram(const css::uno::Reference<css::drawing::XShape>& rXShape, int nDiagramId) |
6487 | 0 | { |
6488 | 0 | uno::Reference<beans::XPropertySet> xPropSet(rXShape, uno::UNO_QUERY); |
6489 | |
|
6490 | 0 | uno::Reference<xml::dom::XDocument> dataDom; |
6491 | 0 | uno::Reference<xml::dom::XDocument> layoutDom; |
6492 | 0 | uno::Reference<xml::dom::XDocument> styleDom; |
6493 | 0 | uno::Reference<xml::dom::XDocument> colorDom; |
6494 | 0 | uno::Reference<xml::dom::XDocument> drawingDom; |
6495 | 0 | uno::Sequence<uno::Sequence<uno::Any>> xDataRelSeq; |
6496 | 0 | uno::Sequence<uno::Any> diagramDrawing; |
6497 | | |
6498 | | // retrieve the doms from the GrabBag |
6499 | 0 | uno::Sequence<beans::PropertyValue> propList; |
6500 | 0 | xPropSet->getPropertyValue(UNO_NAME_MISC_OBJ_INTEROPGRABBAG) >>= propList; |
6501 | 0 | for (const auto& rProp : propList) |
6502 | 0 | { |
6503 | 0 | OUString propName = rProp.Name; |
6504 | 0 | if (propName == "OOXData") |
6505 | 0 | rProp.Value >>= dataDom; |
6506 | 0 | else if (propName == "OOXLayout") |
6507 | 0 | rProp.Value >>= layoutDom; |
6508 | 0 | else if (propName == "OOXStyle") |
6509 | 0 | rProp.Value >>= styleDom; |
6510 | 0 | else if (propName == "OOXColor") |
6511 | 0 | rProp.Value >>= colorDom; |
6512 | 0 | else if (propName == "OOXDrawing") |
6513 | 0 | { |
6514 | 0 | rProp.Value >>= diagramDrawing; |
6515 | 0 | diagramDrawing[0] |
6516 | 0 | >>= drawingDom; // if there is OOXDrawing property then set drawingDom here only. |
6517 | 0 | } |
6518 | 0 | else if (propName == "OOXDiagramDataRels") |
6519 | 0 | rProp.Value >>= xDataRelSeq; |
6520 | 0 | } |
6521 | | |
6522 | | // check that we have the 4 mandatory XDocuments |
6523 | | // if not, there was an error importing and we won't output anything |
6524 | 0 | if (!dataDom.is() || !layoutDom.is() || !styleDom.is() || !colorDom.is()) |
6525 | 0 | return; |
6526 | | |
6527 | | // generate a unique id |
6528 | 0 | rtl::Reference<sax_fastparser::FastAttributeList> pDocPrAttrList |
6529 | 0 | = sax_fastparser::FastSerializerHelper::createAttrList(); |
6530 | 0 | pDocPrAttrList->add(XML_id, OString::number(nDiagramId)); |
6531 | 0 | OString sName = "Diagram" + OString::number(nDiagramId); |
6532 | 0 | pDocPrAttrList->add(XML_name, sName); |
6533 | |
|
6534 | 0 | if (GetDocumentType() == DOCUMENT_DOCX) |
6535 | 0 | { |
6536 | 0 | mpFS->singleElementNS(XML_wp, XML_docPr, pDocPrAttrList); |
6537 | 0 | mpFS->singleElementNS(XML_wp, XML_cNvGraphicFramePr); |
6538 | |
|
6539 | 0 | mpFS->startElementNS(XML_a, XML_graphic, FSNS(XML_xmlns, XML_a), |
6540 | 0 | mpFB->getNamespaceURL(OOX_NS(dml))); |
6541 | 0 | } |
6542 | 0 | else |
6543 | 0 | { |
6544 | 0 | mpFS->startElementNS(XML_p, XML_nvGraphicFramePr); |
6545 | |
|
6546 | 0 | mpFS->singleElementNS(XML_p, XML_cNvPr, pDocPrAttrList); |
6547 | 0 | mpFS->singleElementNS(XML_p, XML_cNvGraphicFramePr); |
6548 | |
|
6549 | 0 | mpFS->startElementNS(XML_p, XML_nvPr); |
6550 | 0 | mpFS->startElementNS(XML_p, XML_extLst); |
6551 | | // change tracking extension - required in PPTX |
6552 | 0 | mpFS->startElementNS(XML_p, XML_ext, XML_uri, "{D42A27DB-BD31-4B8C-83A1-F6EECF244321}"); |
6553 | 0 | mpFS->singleElementNS(XML_p14, XML_modId, |
6554 | 0 | FSNS(XML_xmlns, XML_p14), mpFB->getNamespaceURL(OOX_NS(p14)), |
6555 | 0 | XML_val, |
6556 | 0 | OString::number(comphelper::rng::uniform_uint_distribution(1, SAL_MAX_UINT32))); |
6557 | 0 | mpFS->endElementNS(XML_p, XML_ext); |
6558 | 0 | mpFS->endElementNS(XML_p, XML_extLst); |
6559 | 0 | mpFS->endElementNS(XML_p, XML_nvPr); |
6560 | |
|
6561 | 0 | mpFS->endElementNS(XML_p, XML_nvGraphicFramePr); |
6562 | | |
6563 | | // store size and position of background shape instead of group shape |
6564 | | // as some shapes may be outside |
6565 | 0 | css::uno::Reference<css::drawing::XShapes> xShapes(rXShape, uno::UNO_QUERY); |
6566 | 0 | if (xShapes.is() && xShapes->hasElements()) |
6567 | 0 | { |
6568 | 0 | css::uno::Reference<css::drawing::XShape> xShapeBg(xShapes->getByIndex(0), |
6569 | 0 | uno::UNO_QUERY); |
6570 | 0 | awt::Point aPos = xShapeBg->getPosition(); |
6571 | 0 | awt::Size aSize = xShapeBg->getSize(); |
6572 | 0 | WriteTransformation( |
6573 | 0 | xShapeBg, tools::Rectangle(Point(aPos.X, aPos.Y), Size(aSize.Width, aSize.Height)), |
6574 | 0 | XML_p, false, false, 0, false); |
6575 | 0 | } |
6576 | |
|
6577 | 0 | mpFS->startElementNS(XML_a, XML_graphic); |
6578 | 0 | } |
6579 | |
|
6580 | 0 | mpFS->startElementNS(XML_a, XML_graphicData, XML_uri, |
6581 | 0 | "http://schemas.openxmlformats.org/drawingml/2006/diagram"); |
6582 | |
|
6583 | 0 | OUString sRelationCompPrefix = GetRelationCompPrefix(); |
6584 | | |
6585 | | // add data relation |
6586 | 0 | OUString dataFileName = "diagrams/data" + OUString::number(nDiagramId) + ".xml"; |
6587 | 0 | OUString dataRelId = |
6588 | 0 | mpFB->addRelation(mpFS->getOutputStream(), oox::getRelationship(Relationship::DIAGRAMDATA), |
6589 | 0 | Concat2View(sRelationCompPrefix + dataFileName)); |
6590 | | |
6591 | | // add layout relation |
6592 | 0 | OUString layoutFileName = "diagrams/layout" + OUString::number(nDiagramId) + ".xml"; |
6593 | 0 | OUString layoutRelId = mpFB->addRelation(mpFS->getOutputStream(), |
6594 | 0 | oox::getRelationship(Relationship::DIAGRAMLAYOUT), |
6595 | 0 | Concat2View(sRelationCompPrefix + layoutFileName)); |
6596 | | |
6597 | | // add style relation |
6598 | 0 | OUString styleFileName = "diagrams/quickStyle" + OUString::number(nDiagramId) + ".xml"; |
6599 | 0 | OUString styleRelId = mpFB->addRelation(mpFS->getOutputStream(), |
6600 | 0 | oox::getRelationship(Relationship::DIAGRAMQUICKSTYLE), |
6601 | 0 | Concat2View(sRelationCompPrefix + styleFileName)); |
6602 | | |
6603 | | // add color relation |
6604 | 0 | OUString colorFileName = "diagrams/colors" + OUString::number(nDiagramId) + ".xml"; |
6605 | 0 | OUString colorRelId = mpFB->addRelation(mpFS->getOutputStream(), |
6606 | 0 | oox::getRelationship(Relationship::DIAGRAMCOLORS), |
6607 | 0 | Concat2View(sRelationCompPrefix + colorFileName)); |
6608 | |
|
6609 | 0 | OUString drawingFileName; |
6610 | 0 | if (drawingDom.is()) |
6611 | 0 | { |
6612 | | // add drawing relation |
6613 | 0 | drawingFileName = "diagrams/drawing" + OUString::number(nDiagramId) + ".xml"; |
6614 | 0 | OUString drawingRelId = mpFB->addRelation( |
6615 | 0 | mpFS->getOutputStream(), oox::getRelationship(Relationship::DIAGRAMDRAWING), |
6616 | 0 | Concat2View(sRelationCompPrefix + drawingFileName)); |
6617 | | |
6618 | | // the data dom contains a reference to the drawing relation. We need to update it with the new generated |
6619 | | // relation value before writing the dom to a file |
6620 | | |
6621 | | // Get the dsp:damaModelExt node from the dom |
6622 | 0 | uno::Reference<xml::dom::XNodeList> nodeList = dataDom->getElementsByTagNameNS( |
6623 | 0 | u"http://schemas.microsoft.com/office/drawing/2008/diagram"_ustr, u"dataModelExt"_ustr); |
6624 | | |
6625 | | // There must be one element only so get it |
6626 | 0 | uno::Reference<xml::dom::XNode> node = nodeList->item(0); |
6627 | | |
6628 | | // Get the list of attributes of the node |
6629 | 0 | uno::Reference<xml::dom::XNamedNodeMap> nodeMap = node->getAttributes(); |
6630 | | |
6631 | | // Get the node with the relId attribute and set its new value |
6632 | 0 | uno::Reference<xml::dom::XNode> relIdNode = nodeMap->getNamedItem(u"relId"_ustr); |
6633 | 0 | relIdNode->setNodeValue(drawingRelId); |
6634 | 0 | } |
6635 | |
|
6636 | 0 | mpFS->singleElementNS(XML_dgm, XML_relIds, |
6637 | 0 | FSNS(XML_xmlns, XML_dgm), mpFB->getNamespaceURL(OOX_NS(dmlDiagram)), |
6638 | 0 | FSNS(XML_xmlns, XML_r), mpFB->getNamespaceURL(OOX_NS(officeRel)), |
6639 | 0 | FSNS(XML_r, XML_dm), dataRelId, FSNS(XML_r, XML_lo), layoutRelId, |
6640 | 0 | FSNS(XML_r, XML_qs), styleRelId, FSNS(XML_r, XML_cs), colorRelId); |
6641 | |
|
6642 | 0 | mpFS->endElementNS(XML_a, XML_graphicData); |
6643 | 0 | mpFS->endElementNS(XML_a, XML_graphic); |
6644 | |
|
6645 | 0 | uno::Reference<xml::sax::XSAXSerializable> serializer; |
6646 | 0 | uno::Reference<xml::sax::XWriter> writer |
6647 | 0 | = xml::sax::Writer::create(comphelper::getProcessComponentContext()); |
6648 | |
|
6649 | 0 | OUString sDir = GetComponentDir(); |
6650 | | |
6651 | | // write data file |
6652 | 0 | serializer.set(dataDom, uno::UNO_QUERY); |
6653 | 0 | uno::Reference<io::XOutputStream> xDataOutputStream = mpFB->openFragmentStream( |
6654 | 0 | sDir + "/" + dataFileName, |
6655 | 0 | u"application/vnd.openxmlformats-officedocument.drawingml.diagramData+xml"_ustr); |
6656 | 0 | writer->setOutputStream(xDataOutputStream); |
6657 | 0 | serializer->serialize(uno::Reference<xml::sax::XDocumentHandler>(writer, uno::UNO_QUERY_THROW), |
6658 | 0 | uno::Sequence<beans::StringPair>()); |
6659 | | |
6660 | | // write the associated Images and rels for data file |
6661 | 0 | writeDiagramRels(xDataRelSeq, xDataOutputStream, u"OOXDiagramDataRels", nDiagramId); |
6662 | | |
6663 | | // write layout file |
6664 | 0 | serializer.set(layoutDom, uno::UNO_QUERY); |
6665 | 0 | writer->setOutputStream(mpFB->openFragmentStream( |
6666 | 0 | sDir + "/" + layoutFileName, |
6667 | 0 | u"application/vnd.openxmlformats-officedocument.drawingml.diagramLayout+xml"_ustr)); |
6668 | 0 | serializer->serialize(uno::Reference<xml::sax::XDocumentHandler>(writer, uno::UNO_QUERY_THROW), |
6669 | 0 | uno::Sequence<beans::StringPair>()); |
6670 | | |
6671 | | // write style file |
6672 | 0 | serializer.set(styleDom, uno::UNO_QUERY); |
6673 | 0 | writer->setOutputStream(mpFB->openFragmentStream( |
6674 | 0 | sDir + "/" + styleFileName, |
6675 | 0 | u"application/vnd.openxmlformats-officedocument.drawingml.diagramStyle+xml"_ustr)); |
6676 | 0 | serializer->serialize(uno::Reference<xml::sax::XDocumentHandler>(writer, uno::UNO_QUERY_THROW), |
6677 | 0 | uno::Sequence<beans::StringPair>()); |
6678 | | |
6679 | | // write color file |
6680 | 0 | serializer.set(colorDom, uno::UNO_QUERY); |
6681 | 0 | writer->setOutputStream(mpFB->openFragmentStream( |
6682 | 0 | sDir + "/" + colorFileName, |
6683 | 0 | u"application/vnd.openxmlformats-officedocument.drawingml.diagramColors+xml"_ustr)); |
6684 | 0 | serializer->serialize(uno::Reference<xml::sax::XDocumentHandler>(writer, uno::UNO_QUERY_THROW), |
6685 | 0 | uno::Sequence<beans::StringPair>()); |
6686 | | |
6687 | | // write drawing file |
6688 | 0 | if (!drawingDom.is()) |
6689 | 0 | return; |
6690 | | |
6691 | 0 | serializer.set(drawingDom, uno::UNO_QUERY); |
6692 | 0 | uno::Reference<io::XOutputStream> xDrawingOutputStream = mpFB->openFragmentStream( |
6693 | 0 | sDir + "/" + drawingFileName, u"application/vnd.ms-office.drawingml.diagramDrawing+xml"_ustr); |
6694 | 0 | writer->setOutputStream(xDrawingOutputStream); |
6695 | 0 | serializer->serialize( |
6696 | 0 | uno::Reference<xml::sax::XDocumentHandler>(writer, uno::UNO_QUERY_THROW), |
6697 | 0 | uno::Sequence<beans::StringPair>()); |
6698 | | |
6699 | | // write the associated Images and rels for drawing file |
6700 | 0 | uno::Sequence<uno::Sequence<uno::Any>> xDrawingRelSeq; |
6701 | 0 | diagramDrawing[1] >>= xDrawingRelSeq; |
6702 | 0 | writeDiagramRels(xDrawingRelSeq, xDrawingOutputStream, u"OOXDiagramDrawingRels", nDiagramId); |
6703 | 0 | } |
6704 | | |
6705 | | void DrawingML::writeDiagramRels(const uno::Sequence<uno::Sequence<uno::Any>>& xRelSeq, |
6706 | | const uno::Reference<io::XOutputStream>& xOutStream, |
6707 | | std::u16string_view sGrabBagProperyName, int nDiagramId) |
6708 | 0 | { |
6709 | | // add image relationships of OOXData, OOXDiagram |
6710 | 0 | OUString sType(oox::getRelationship(Relationship::IMAGE)); |
6711 | 0 | uno::Reference<xml::sax::XWriter> xWriter |
6712 | 0 | = xml::sax::Writer::create(comphelper::getProcessComponentContext()); |
6713 | 0 | xWriter->setOutputStream(xOutStream); |
6714 | | |
6715 | | // retrieve the relationships from Sequence |
6716 | 0 | for (sal_Int32 j = 0; j < xRelSeq.getLength(); j++) |
6717 | 0 | { |
6718 | | // diagramDataRelTuple[0] => RID, |
6719 | | // diagramDataRelTuple[1] => xInputStream |
6720 | | // diagramDataRelTuple[2] => extension |
6721 | 0 | const uno::Sequence<uno::Any>& diagramDataRelTuple = xRelSeq[j]; |
6722 | |
|
6723 | 0 | OUString sRelId; |
6724 | 0 | OUString sExtension; |
6725 | 0 | diagramDataRelTuple[0] >>= sRelId; |
6726 | 0 | diagramDataRelTuple[2] >>= sExtension; |
6727 | 0 | OUString sContentType; |
6728 | 0 | if (sExtension.equalsIgnoreAsciiCase(".WMF")) |
6729 | 0 | sContentType = "image/x-wmf"; |
6730 | 0 | else |
6731 | 0 | sContentType = OUString::Concat("image/") + sExtension.subView(1); |
6732 | 0 | sRelId = sRelId.copy(3); |
6733 | |
|
6734 | 0 | StreamDataSequence dataSeq; |
6735 | 0 | diagramDataRelTuple[1] >>= dataSeq; |
6736 | 0 | uno::Reference<io::XInputStream> dataImagebin( |
6737 | 0 | new ::comphelper::SequenceInputStream(dataSeq)); |
6738 | | |
6739 | | //nDiagramId is used to make the name unique irrespective of the number of smart arts. |
6740 | 0 | OUString sFragment = OUString::Concat("media/") + sGrabBagProperyName |
6741 | 0 | + OUString::number(nDiagramId) + "_" |
6742 | 0 | + OUString::number(j) + sExtension; |
6743 | |
|
6744 | 0 | PropertySet aProps(xOutStream); |
6745 | 0 | aProps.setAnyProperty(PROP_RelId, uno::Any(sRelId.toInt32())); |
6746 | |
|
6747 | 0 | mpFB->addRelation(xOutStream, sType, Concat2View("../" + sFragment)); |
6748 | |
|
6749 | 0 | OUString sDir = GetComponentDir(); |
6750 | 0 | uno::Reference<io::XOutputStream> xBinOutStream |
6751 | 0 | = mpFB->openFragmentStream(sDir + "/" + sFragment, sContentType); |
6752 | |
|
6753 | 0 | try |
6754 | 0 | { |
6755 | 0 | comphelper::OStorageHelper::CopyInputToOutput(dataImagebin, xBinOutStream); |
6756 | 0 | } |
6757 | 0 | catch (const uno::Exception&) |
6758 | 0 | { |
6759 | 0 | TOOLS_WARN_EXCEPTION("oox.drawingml", "DrawingML::writeDiagramRels Failed to copy grabbaged Image"); |
6760 | 0 | } |
6761 | 0 | dataImagebin->closeInput(); |
6762 | 0 | } |
6763 | 0 | } |
6764 | | |
6765 | | void DrawingML::WriteFromTo(const uno::Reference<css::drawing::XShape>& rXShape, const awt::Size& aPageSize, |
6766 | | const FSHelperPtr& pDrawing) |
6767 | 0 | { |
6768 | 0 | awt::Point aTopLeft = rXShape->getPosition(); |
6769 | 0 | awt::Size aSize = rXShape->getSize(); |
6770 | |
|
6771 | 0 | SdrObject* pObj = SdrObject::getSdrObjectFromXShape(rXShape); |
6772 | 0 | if (pObj) |
6773 | 0 | { |
6774 | 0 | Degree100 nRotation = pObj->GetRotateAngle(); |
6775 | 0 | if (nRotation) |
6776 | 0 | { |
6777 | 0 | sal_Int16 nHalfWidth = aSize.Width / 2; |
6778 | 0 | sal_Int16 nHalfHeight = aSize.Height / 2; |
6779 | | // aTopLeft needs correction for rotated customshapes |
6780 | 0 | if (pObj->GetObjIdentifier() == SdrObjKind::CustomShape) |
6781 | 0 | { |
6782 | | // Center of bounding box of the rotated shape |
6783 | 0 | const auto aSnapRectCenter(pObj->GetSnapRect().Center()); |
6784 | 0 | aTopLeft.X = aSnapRectCenter.X() - nHalfWidth; |
6785 | 0 | aTopLeft.Y = aSnapRectCenter.Y() - nHalfHeight; |
6786 | 0 | } |
6787 | | |
6788 | | // MSO changes the anchor positions at these angles and that does an extra 90 degrees |
6789 | | // rotation on our shapes, so we output it in such position that MSO |
6790 | | // can draw this shape correctly. |
6791 | 0 | if ((nRotation >= 4500_deg100 && nRotation < 13500_deg100) || (nRotation >= 22500_deg100 && nRotation < 31500_deg100)) |
6792 | 0 | { |
6793 | 0 | aTopLeft.X = aTopLeft.X - nHalfHeight + nHalfWidth; |
6794 | 0 | aTopLeft.Y = aTopLeft.Y - nHalfWidth + nHalfHeight; |
6795 | |
|
6796 | 0 | std::swap(aSize.Width, aSize.Height); |
6797 | 0 | } |
6798 | 0 | } |
6799 | 0 | } |
6800 | |
|
6801 | 0 | tools::Rectangle aLocation(aTopLeft.X, aTopLeft.Y, aTopLeft.X + aSize.Width, aTopLeft.Y + aSize.Height); |
6802 | 0 | double nXpos = static_cast<double>(aLocation.TopLeft().getX()) / static_cast<double>(aPageSize.Width); |
6803 | 0 | double nYpos = static_cast<double>(aLocation.TopLeft().getY()) / static_cast<double>(aPageSize.Height); |
6804 | |
|
6805 | 0 | pDrawing->startElement(FSNS(XML_cdr, XML_from)); |
6806 | 0 | pDrawing->startElement(FSNS(XML_cdr, XML_x)); |
6807 | 0 | pDrawing->write(nXpos); |
6808 | 0 | pDrawing->endElement(FSNS(XML_cdr, XML_x)); |
6809 | 0 | pDrawing->startElement(FSNS(XML_cdr, XML_y)); |
6810 | 0 | pDrawing->write(nYpos); |
6811 | 0 | pDrawing->endElement(FSNS(XML_cdr, XML_y)); |
6812 | 0 | pDrawing->endElement(FSNS(XML_cdr, XML_from)); |
6813 | |
|
6814 | 0 | nXpos = static_cast<double>(aLocation.BottomRight().getX()) / static_cast<double>(aPageSize.Width); |
6815 | 0 | nYpos = static_cast<double>(aLocation.BottomRight().getY()) / static_cast<double>(aPageSize.Height); |
6816 | |
|
6817 | 0 | pDrawing->startElement(FSNS(XML_cdr, XML_to)); |
6818 | 0 | pDrawing->startElement(FSNS(XML_cdr, XML_x)); |
6819 | 0 | pDrawing->write(nXpos); |
6820 | 0 | pDrawing->endElement(FSNS(XML_cdr, XML_x)); |
6821 | 0 | pDrawing->startElement(FSNS(XML_cdr, XML_y)); |
6822 | 0 | pDrawing->write(nYpos); |
6823 | 0 | pDrawing->endElement(FSNS(XML_cdr, XML_y)); |
6824 | 0 | pDrawing->endElement(FSNS(XML_cdr, XML_to)); |
6825 | 0 | } |
6826 | | |
6827 | | } |
6828 | | |
6829 | | /* vim:set shiftwidth=4 softtabstop=4 expandtab: */ |