LibreOffice Module oox (master) 1
drawingml.cxx
Go to the documentation of this file.
1/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2/*
3 * This file is part of the LibreOffice project.
4 *
5 * This Source Code Form is subject to the terms of the Mozilla Public
6 * License, v. 2.0. If a copy of the MPL was not distributed with this
7 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
8 *
9 * This file incorporates work covered by the following license notice:
10 *
11 * Licensed to the Apache Software Foundation (ASF) under one or more
12 * contributor license agreements. See the NOTICE file distributed
13 * with this work for additional information regarding copyright
14 * ownership. The ASF licenses this file to you under the Apache
15 * License, Version 2.0 (the "License"); you may not use this file
16 * except in compliance with the License. You may obtain a copy of
17 * the License at http://www.apache.org/licenses/LICENSE-2.0 .
18 */
19
20#include <config_features.h>
21
22#include <config_folders.h>
23#include <rtl/bootstrap.hxx>
24#include <sal/log.hxx>
27#include <oox/export/utils.hxx>
32#include <oox/token/namespaces.hxx>
33#include <oox/token/properties.hxx>
35#include <oox/token/tokens.hxx>
37#include <svtools/unitconv.hxx>
38#include <sax/fastattribs.hxx>
44
45#include <numeric>
46#include <string_view>
47
48#include <com/sun/star/awt/CharSet.hpp>
49#include <com/sun/star/awt/FontDescriptor.hpp>
50#include <com/sun/star/awt/FontSlant.hpp>
51#include <com/sun/star/awt/FontStrikeout.hpp>
52#include <com/sun/star/awt/FontWeight.hpp>
53#include <com/sun/star/awt/FontUnderline.hpp>
54#include <com/sun/star/awt/Gradient.hpp>
55#include <com/sun/star/beans/XPropertySet.hpp>
56#include <com/sun/star/beans/XPropertyState.hpp>
57#include <com/sun/star/beans/XPropertySetInfo.hpp>
58#include <com/sun/star/container/XEnumerationAccess.hpp>
59#include <com/sun/star/container/XIndexAccess.hpp>
60#include <com/sun/star/container/XNameAccess.hpp>
61#include <com/sun/star/drawing/BitmapMode.hpp>
62#include <com/sun/star/drawing/ColorMode.hpp>
63#include <com/sun/star/drawing/EnhancedCustomShapeAdjustmentValue.hpp>
64#include <com/sun/star/drawing/EnhancedCustomShapeParameterPair.hpp>
65#include <com/sun/star/drawing/EnhancedCustomShapeParameterType.hpp>
66#include <com/sun/star/drawing/EnhancedCustomShapeSegment.hpp>
67#include <com/sun/star/drawing/EnhancedCustomShapeSegmentCommand.hpp>
68#include <com/sun/star/drawing/Hatch.hpp>
69#include <com/sun/star/drawing/LineDash.hpp>
70#include <com/sun/star/drawing/LineJoint.hpp>
71#include <com/sun/star/drawing/LineStyle.hpp>
72#include <com/sun/star/drawing/TextFitToSizeType.hpp>
73#include <com/sun/star/drawing/TextHorizontalAdjust.hpp>
74#include <com/sun/star/drawing/TextVerticalAdjust.hpp>
75#include <com/sun/star/drawing/XShape.hpp>
76#include <com/sun/star/drawing/XShapes.hpp>
77#include <com/sun/star/drawing/FillStyle.hpp>
78#include <com/sun/star/frame/XModel.hpp>
79#include <com/sun/star/graphic/XGraphic.hpp>
80#include <com/sun/star/i18n/ScriptType.hpp>
81#include <com/sun/star/i18n/BreakIterator.hpp>
82#include <com/sun/star/i18n/XBreakIterator.hpp>
83#include <com/sun/star/io/XOutputStream.hpp>
84#include <com/sun/star/lang/XMultiServiceFactory.hpp>
85#include <com/sun/star/style/LineSpacing.hpp>
86#include <com/sun/star/style/LineSpacingMode.hpp>
87#include <com/sun/star/text/WritingMode.hpp>
88#include <com/sun/star/text/WritingMode2.hpp>
89#include <com/sun/star/text/GraphicCrop.hpp>
90#include <com/sun/star/text/XText.hpp>
91#include <com/sun/star/text/XTextColumns.hpp>
92#include <com/sun/star/text/XTextContent.hpp>
93#include <com/sun/star/text/XTextField.hpp>
94#include <com/sun/star/text/XTextRange.hpp>
95#include <com/sun/star/text/XTextFrame.hpp>
96#include <com/sun/star/style/CaseMap.hpp>
97#include <com/sun/star/xml/dom/XNodeList.hpp>
98#include <com/sun/star/xml/sax/Writer.hpp>
99#include <com/sun/star/xml/sax/XSAXSerializable.hpp>
100#include <com/sun/star/container/XNamed.hpp>
101#include <com/sun/star/drawing/XDrawPages.hpp>
102#include <com/sun/star/drawing/XDrawPagesSupplier.hpp>
103
105#include <comphelper/random.hxx>
109#include <o3tl/any.hxx>
110#include <o3tl/safeint.hxx>
111#include <o3tl/string_view.hxx>
112#include <tools/stream.hxx>
113#include <unotools/fontdefs.hxx>
114#include <vcl/cvtgrf.hxx>
115#include <vcl/svapp.hxx>
116#include <rtl/strbuf.hxx>
119#include <editeng/outlobj.hxx>
120#include <editeng/svxenum.hxx>
121#include <editeng/unonames.hxx>
122#include <editeng/unoprnms.hxx>
123#include <editeng/flditem.hxx>
125#include <editeng/unonrule.hxx>
126#include <svx/svdoashp.hxx>
127#include <svx/svdomedia.hxx>
128#include <svx/svdtrans.hxx>
129#include <svx/unoshape.hxx>
132
133using namespace ::css;
134using namespace ::css::beans;
135using namespace ::css::drawing;
136using namespace ::css::i18n;
137using namespace ::css::style;
138using namespace ::css::text;
139using namespace ::css::uno;
140using namespace ::css::container;
141using namespace ::com::sun::star::drawing::EnhancedCustomShapeSegmentCommand;
142
143using ::css::io::XOutputStream;
144using ::sax_fastparser::FSHelperPtr;
145using ::sax_fastparser::FastSerializerHelper;
146
147namespace
148{
150sal_Int32 GetAlphaFromTransparenceGradient(const awt::Gradient& rGradient, bool bStart)
151{
152 // Our alpha is a gray color value.
153 sal_uInt8 nRed = ::Color(ColorTransparency, bStart ? rGradient.StartColor : rGradient.EndColor).GetRed();
154 // drawingML alpha is a percentage on a 0..100000 scale.
155 return (255 - nRed) * oox::drawingml::MAX_PERCENT / 255;
156}
157
158const char* g_aPredefinedClrNames[] = {
159 "dk1",
160 "lt1",
161 "dk2",
162 "lt2",
163 "accent1",
164 "accent2",
165 "accent3",
166 "accent4",
167 "accent5",
168 "accent6",
169 "hlink",
170 "folHlink",
171};
172}
173
174namespace oox::drawingml {
175
177{
178}
179
180OUString URLTransformer::getTransformedString(const OUString& rString) const
181{
182 return rString;
183}
184
185bool URLTransformer::isExternalURL(const OUString& rURL) const
186{
187 bool bExternal = true;
188 if (rURL.startsWith("#"))
189 bExternal = false;
190 return bExternal;
191}
192
193static css::uno::Any getLineDash( const css::uno::Reference<css::frame::XModel>& xModel, const OUString& rDashName )
194 {
195 css::uno::Reference<css::lang::XMultiServiceFactory> xFact(xModel, css::uno::UNO_QUERY);
196 css::uno::Reference<css::container::XNameAccess> xNameAccess(
197 xFact->createInstance("com.sun.star.drawing.DashTable"),
198 css::uno::UNO_QUERY );
199 if(xNameAccess.is())
200 {
201 if (!xNameAccess->hasByName(rDashName))
202 return css::uno::Any();
203
204 return xNameAccess->getByName(rDashName);
205 }
206
207 return css::uno::Any();
208 }
209
210namespace
211{
212void WriteGradientPath(const awt::Gradient& rGradient, const FSHelperPtr& pFS, const bool bCircle)
213{
214 pFS->startElementNS(XML_a, XML_path, XML_path, bCircle ? "circle" : "rect");
215
216 // Write the focus rectangle. Work with the focus point, and assume
217 // that it extends 50% in all directions. The below
218 // left/top/right/bottom values are percentages, where 0 means the
219 // edge of the tile rectangle and 100% means the center of it.
222 sal_Int32 nLeftPercent = rGradient.XOffset;
223 pAttributeList->add(XML_l, OString::number(nLeftPercent * PER_PERCENT));
224 sal_Int32 nTopPercent = rGradient.YOffset;
225 pAttributeList->add(XML_t, OString::number(nTopPercent * PER_PERCENT));
226 sal_Int32 nRightPercent = 100 - rGradient.XOffset;
227 pAttributeList->add(XML_r, OString::number(nRightPercent * PER_PERCENT));
228 sal_Int32 nBottomPercent = 100 - rGradient.YOffset;
229 pAttributeList->add(XML_b, OString::number(nBottomPercent * PER_PERCENT));
230 pFS->singleElementNS(XML_a, XML_fillToRect, pAttributeList);
231
232 pFS->endElementNS(XML_a, XML_path);
233}
234}
235
236// not thread safe
239std::map<OUString, OUString> DrawingML::maWdpCache;
240sal_Int32 DrawingML::mnDrawingMLCount = 0;
241sal_Int32 DrawingML::mnVmlCount = 0;
242std::stack<std::unordered_map<BitmapChecksum, OUString>> DrawingML::maExportGraphics;
243
244sal_Int16 DrawingML::GetScriptType(const OUString& rStr)
245{
246 if (rStr.getLength() > 0)
247 {
248 static Reference<css::i18n::XBreakIterator> xBreakIterator =
249 css::i18n::BreakIterator::create(comphelper::getProcessComponentContext());
250
251 sal_Int16 nScriptType = xBreakIterator->getScriptType(rStr, 0);
252
253 if (nScriptType == css::i18n::ScriptType::WEAK)
254 {
255 sal_Int32 nPos = xBreakIterator->nextScript(rStr, 0, nScriptType);
256 if (nPos < rStr.getLength())
257 nScriptType = xBreakIterator->getScriptType(rStr, nPos);
258
259 }
260
261 if (nScriptType != css::i18n::ScriptType::WEAK)
262 return nScriptType;
263 }
264
265 return css::i18n::ScriptType::LATIN;
266}
267
269{
270 mnImageCounter = 1;
272 maWdpCache.clear();
273}
274
276{
278 mnVmlCount = 0;
279}
280
282{
283 maExportGraphics.emplace();
284}
285
287{
288 maExportGraphics.pop();
289}
290
291bool DrawingML::GetProperty( const Reference< XPropertySet >& rXPropertySet, const OUString& aName )
292{
293 try
294 {
295 mAny = rXPropertySet->getPropertyValue(aName);
296 if (mAny.hasValue())
297 return true;
298 }
299 catch( const Exception& )
300 {
301 /* printf ("exception when trying to get value of property: %s\n", aName.toUtf8()); */
302 }
303 return false;
304}
305
306bool DrawingML::GetPropertyAndState( const Reference< XPropertySet >& rXPropertySet, const Reference< XPropertyState >& rXPropertyState, const OUString& aName, PropertyState& eState )
307{
308 try
309 {
310 mAny = rXPropertySet->getPropertyValue(aName);
311 if (mAny.hasValue())
312 {
313 eState = rXPropertyState->getPropertyState(aName);
314 return true;
315 }
316 }
317 catch( const Exception& )
318 {
319 /* printf ("exception when trying to get value of property: %s\n", aName.toUtf8()); */
320 }
321 return false;
322}
323
324namespace
325{
327OString getColorStr(const ::Color nColor)
328{
329 // Transparency is a separate element.
330 OString sColor = OString::number(sal_uInt32(nColor) & 0x00FFFFFF, 16);
331 if (sColor.getLength() < 6)
332 {
333 OStringBuffer sBuf("0");
334 int remains = 5 - sColor.getLength();
335
336 while (remains > 0)
337 {
338 sBuf.append("0");
339 remains--;
340 }
341
342 sBuf.append(sColor);
343
344 sColor = sBuf.getStr();
345 }
346 return sColor;
347}
348}
349
350void DrawingML::WriteColor( ::Color nColor, sal_Int32 nAlpha )
351{
352 const auto sColor = getColorStr(nColor);
353 if( nAlpha < MAX_PERCENT )
354 {
355 mpFS->startElementNS(XML_a, XML_srgbClr, XML_val, sColor);
356 mpFS->singleElementNS(XML_a, XML_alpha, XML_val, OString::number(nAlpha));
357 mpFS->endElementNS( XML_a, XML_srgbClr );
358
359 }
360 else
361 {
362 mpFS->singleElementNS(XML_a, XML_srgbClr, XML_val, sColor);
363 }
364}
365
366void DrawingML::WriteColor( const OUString& sColorSchemeName, const Sequence< PropertyValue >& aTransformations, sal_Int32 nAlpha )
367{
368 // prevent writing a tag with empty val attribute
369 if( sColorSchemeName.isEmpty() )
370 return;
371
372 if( aTransformations.hasElements() )
373 {
374 mpFS->startElementNS(XML_a, XML_schemeClr, XML_val, sColorSchemeName);
375 WriteColorTransformations( aTransformations, nAlpha );
376 mpFS->endElementNS( XML_a, XML_schemeClr );
377 }
378 else if(nAlpha < MAX_PERCENT)
379 {
380 mpFS->startElementNS(XML_a, XML_schemeClr, XML_val, sColorSchemeName);
381 mpFS->singleElementNS(XML_a, XML_alpha, XML_val, OString::number(nAlpha));
382 mpFS->endElementNS( XML_a, XML_schemeClr );
383 }
384 else
385 {
386 mpFS->singleElementNS(XML_a, XML_schemeClr, XML_val, sColorSchemeName);
387 }
388}
389
390void DrawingML::WriteColor( const ::Color nColor, const Sequence< PropertyValue >& aTransformations, sal_Int32 nAlpha )
391{
392 const auto sColor = getColorStr(nColor);
393 if( aTransformations.hasElements() )
394 {
395 mpFS->startElementNS(XML_a, XML_srgbClr, XML_val, sColor);
396 WriteColorTransformations(aTransformations, nAlpha);
397 mpFS->endElementNS(XML_a, XML_srgbClr);
398 }
399 else if(nAlpha < MAX_PERCENT)
400 {
401 mpFS->startElementNS(XML_a, XML_srgbClr, XML_val, sColor);
402 mpFS->singleElementNS(XML_a, XML_alpha, XML_val, OString::number(nAlpha));
403 mpFS->endElementNS(XML_a, XML_srgbClr);
404 }
405 else
406 {
407 mpFS->singleElementNS(XML_a, XML_srgbClr, XML_val, sColor);
408 }
409}
410
411void DrawingML::WriteColorTransformations( const Sequence< PropertyValue >& aTransformations, sal_Int32 nAlpha )
412{
413 for( const auto& rTransformation : aTransformations )
414 {
415 sal_Int32 nToken = Color::getColorTransformationToken( rTransformation.Name );
416 if( nToken != XML_TOKEN_INVALID && rTransformation.Value.hasValue() )
417 {
418 if(nToken == XML_alpha && nAlpha < MAX_PERCENT)
419 {
420 mpFS->singleElementNS(XML_a, nToken, XML_val, OString::number(nAlpha));
421 }
422 else
423 {
424 sal_Int32 nValue = rTransformation.Value.get<sal_Int32>();
425 mpFS->singleElementNS(XML_a, nToken, XML_val, OString::number(nValue));
426 }
427 }
428 }
429}
430
431bool DrawingML::WriteCharColor(const css::uno::Reference<css::beans::XPropertySet>& xPropertySet)
432{
433 if (!xPropertySet->getPropertySetInfo()->hasPropertyByName("CharColorTheme"))
434 {
435 return false;
436 }
437
438 sal_Int32 nCharColorTheme = -1;
439 xPropertySet->getPropertyValue("CharColorTheme") >>= nCharColorTheme;
440 if (nCharColorTheme < 0 || nCharColorTheme > 11)
441 {
442 return false;
443 }
444
445 const char* pColorName = g_aPredefinedClrNames[nCharColorTheme];
446
447 sal_Int32 nCharColorTintOrShade{};
448 xPropertySet->getPropertyValue("CharColorTintOrShade") >>= nCharColorTintOrShade;
449 if (nCharColorTintOrShade != 0)
450 {
451 return false;
452 }
453
454 mpFS->startElementNS(XML_a, XML_solidFill);
455 mpFS->startElementNS(XML_a, XML_schemeClr, XML_val, pColorName);
456
457 sal_Int32 nCharColorLumMod{};
458 xPropertySet->getPropertyValue("CharColorLumMod") >>= nCharColorLumMod;
459 if (nCharColorLumMod != 10000)
460 {
461 mpFS->singleElementNS(XML_a, XML_lumMod, XML_val, OString::number(nCharColorLumMod * 10));
462 }
463
464 sal_Int32 nCharColorLumOff{};
465 xPropertySet->getPropertyValue("CharColorLumOff") >>= nCharColorLumOff;
466 if (nCharColorLumOff != 0)
467 {
468 mpFS->singleElementNS(XML_a, XML_lumOff, XML_val, OString::number(nCharColorLumOff * 10));
469 }
470
471 mpFS->endElementNS(XML_a, XML_schemeClr);
472 mpFS->endElementNS(XML_a, XML_solidFill);
473
474 return true;
475}
476
477void DrawingML::WriteSolidFill( ::Color nColor, sal_Int32 nAlpha )
478{
479 mpFS->startElementNS(XML_a, XML_solidFill);
480 WriteColor( nColor, nAlpha );
481 mpFS->endElementNS( XML_a, XML_solidFill );
482}
483
484void DrawingML::WriteSolidFill( const OUString& sSchemeName, const Sequence< PropertyValue >& aTransformations, sal_Int32 nAlpha )
485{
486 mpFS->startElementNS(XML_a, XML_solidFill);
487 WriteColor( sSchemeName, aTransformations, nAlpha );
488 mpFS->endElementNS( XML_a, XML_solidFill );
489}
490
491void DrawingML::WriteSolidFill( const ::Color nColor, const Sequence< PropertyValue >& aTransformations, sal_Int32 nAlpha )
492{
493 mpFS->startElementNS(XML_a, XML_solidFill);
494 WriteColor(nColor, aTransformations, nAlpha);
495 mpFS->endElementNS(XML_a, XML_solidFill);
496}
497
498void DrawingML::WriteSolidFill( const Reference< XPropertySet >& rXPropSet )
499{
500 // get fill color
501 if ( !GetProperty( rXPropSet, "FillColor" ) )
502 return;
503 sal_uInt32 nFillColor = mAny.get<sal_uInt32>();
504
505 // get InteropGrabBag and search the relevant attributes
506 OUString sColorFillScheme;
507 sal_uInt32 nOriginalColor = 0;
508 Sequence< PropertyValue > aStyleProperties, aTransformations;
509 if ( GetProperty( rXPropSet, "InteropGrabBag" ) )
510 {
511 Sequence< PropertyValue > aGrabBag;
512 mAny >>= aGrabBag;
513 for( const auto& rProp : std::as_const(aGrabBag) )
514 {
515 if( rProp.Name == "SpPrSolidFillSchemeClr" )
516 rProp.Value >>= sColorFillScheme;
517 else if( rProp.Name == "OriginalSolidFillClr" )
518 rProp.Value >>= nOriginalColor;
519 else if( rProp.Name == "StyleFillRef" )
520 rProp.Value >>= aStyleProperties;
521 else if( rProp.Name == "SpPrSolidFillSchemeClrTransformations" )
522 rProp.Value >>= aTransformations;
523 }
524 }
525
526 sal_Int32 nAlpha = MAX_PERCENT;
527 if( GetProperty( rXPropSet, "FillTransparence" ) )
528 {
529 sal_Int32 nTransparency = 0;
530 mAny >>= nTransparency;
531 // Calculate alpha value (see oox/source/drawingml/color.cxx : getTransparency())
532 nAlpha = (MAX_PERCENT - ( PER_PERCENT * nTransparency ) );
533 }
534
535 // OOXML has no separate transparence gradient but uses transparency in the gradient stops.
536 // So we merge transparency and color and use gradient fill in such case.
537 awt::Gradient aTransparenceGradient;
538 bool bNeedGradientFill(false);
539 if (GetProperty(rXPropSet, "FillTransparenceGradient"))
540 {
541 mAny >>= aTransparenceGradient;
542 if (aTransparenceGradient.StartColor != aTransparenceGradient.EndColor)
543 bNeedGradientFill = true;
544 else if (aTransparenceGradient.StartColor != 0)
545 nAlpha = GetAlphaFromTransparenceGradient(aTransparenceGradient, true);
546 }
547
548 // write XML
549 if (bNeedGradientFill)
550 {
551 awt::Gradient aPseudoColorGradient;
552 aPseudoColorGradient.XOffset = aTransparenceGradient.XOffset;
553 aPseudoColorGradient.YOffset = aTransparenceGradient.YOffset;
554 aPseudoColorGradient.StartIntensity = 100;
555 aPseudoColorGradient.EndIntensity = 100;
556 aPseudoColorGradient.Angle = aTransparenceGradient.Angle;
557 aPseudoColorGradient.Border = aTransparenceGradient.Border;
558 aPseudoColorGradient.Style = aTransparenceGradient.Style;
559 aPseudoColorGradient.StartColor = nFillColor;
560 aPseudoColorGradient.EndColor = nFillColor;
561 aPseudoColorGradient.StepCount = aTransparenceGradient.StepCount;
562 mpFS->startElementNS(XML_a, XML_gradFill, XML_rotWithShape, "0");
563 WriteGradientFill(aPseudoColorGradient, aTransparenceGradient);
564 mpFS->endElementNS( XML_a, XML_gradFill );
565 }
566 else if ( nFillColor != nOriginalColor )
567 {
568 // the user has set a different color for the shape
569 if (!WriteFillColor(rXPropSet))
570 {
571 WriteSolidFill(::Color(ColorTransparency, nFillColor & 0xffffff), nAlpha);
572 }
573 }
574 else if ( !sColorFillScheme.isEmpty() )
575 {
576 // the shape had a scheme color and the user didn't change it
577 WriteSolidFill( sColorFillScheme, aTransformations, nAlpha );
578 }
579 else
580 {
581 // the shape had a custom color and the user didn't change it
582 // tdf#124013
583 WriteSolidFill( ::Color(ColorTransparency, nFillColor & 0xffffff), nAlpha );
584 }
585}
586
587bool DrawingML::WriteFillColor(const uno::Reference<beans::XPropertySet>& xPropertySet)
588{
589 if (!xPropertySet->getPropertySetInfo()->hasPropertyByName("FillColorTheme"))
590 {
591 return false;
592 }
593
594 sal_Int32 nFillColorTheme = -1;
595 xPropertySet->getPropertyValue("FillColorTheme") >>= nFillColorTheme;
596 if (nFillColorTheme < 0 || nFillColorTheme > 11)
597 {
598 return false;
599 }
600
601 const char* pColorName = g_aPredefinedClrNames[nFillColorTheme];
602
603 mpFS->startElementNS(XML_a, XML_solidFill);
604 mpFS->startElementNS(XML_a, XML_schemeClr, XML_val, pColorName);
605
606 sal_Int32 nFillColorLumMod{};
607 xPropertySet->getPropertyValue("FillColorLumMod") >>= nFillColorLumMod;
608 if (nFillColorLumMod != 10000)
609 {
610 mpFS->singleElementNS(XML_a, XML_lumMod, XML_val, OString::number(nFillColorLumMod * 10));
611 }
612
613 sal_Int32 nFillColorLumOff{};
614 xPropertySet->getPropertyValue("FillColorLumOff") >>= nFillColorLumOff;
615 if (nFillColorLumOff != 0)
616 {
617 mpFS->singleElementNS(XML_a, XML_lumOff, XML_val, OString::number(nFillColorLumOff * 10));
618 }
619
620 mpFS->endElementNS(XML_a, XML_schemeClr);
621 mpFS->endElementNS(XML_a, XML_solidFill);
622
623 return true;
624}
625
626void DrawingML::WriteGradientStop(sal_uInt16 nStop, ::Color nColor, sal_Int32 nAlpha)
627{
628 mpFS->startElementNS(XML_a, XML_gs, XML_pos, OString::number(nStop * 1000));
629 WriteColor(nColor, nAlpha);
630 mpFS->endElementNS( XML_a, XML_gs );
631}
632
633::Color DrawingML::ColorWithIntensity( sal_uInt32 nColor, sal_uInt32 nIntensity )
634{
635 return ::Color(ColorTransparency, ( ( ( nColor & 0xff ) * nIntensity ) / 100 )
636 | ( ( ( ( ( nColor & 0xff00 ) >> 8 ) * nIntensity ) / 100 ) << 8 )
637 | ( ( ( ( ( nColor & 0xff0000 ) >> 8 ) * nIntensity ) / 100 ) << 8 ));
638}
639
640bool DrawingML::EqualGradients( awt::Gradient aGradient1, awt::Gradient aGradient2 )
641{
642 return aGradient1.Style == aGradient2.Style &&
643 aGradient1.StartColor == aGradient2.StartColor &&
644 aGradient1.EndColor == aGradient2.EndColor &&
645 aGradient1.Angle == aGradient2.Angle &&
646 aGradient1.Border == aGradient2.Border &&
647 aGradient1.XOffset == aGradient2.XOffset &&
648 aGradient1.YOffset == aGradient2.YOffset &&
649 aGradient1.StartIntensity == aGradient2.StartIntensity &&
650 aGradient1.EndIntensity == aGradient2.EndIntensity &&
651 aGradient1.StepCount == aGradient2.StepCount;
652}
653
655{
656 awt::Gradient aGradient;
657 if (!GetProperty(rXPropSet, "FillGradient"))
658 return;
659
660 aGradient = *o3tl::doAccess<awt::Gradient>(mAny);
661
662 // get InteropGrabBag and search the relevant attributes
663 awt::Gradient aOriginalGradient;
664 Sequence< PropertyValue > aGradientStops;
665 if ( GetProperty( rXPropSet, "InteropGrabBag" ) )
666 {
668 mAny >>= aGrabBag;
669 for( const auto& rProp : std::as_const(aGrabBag) )
670 if( rProp.Name == "GradFillDefinition" )
671 rProp.Value >>= aGradientStops;
672 else if( rProp.Name == "OriginalGradFill" )
673 rProp.Value >>= aOriginalGradient;
674 }
675
676 // check if an ooxml gradient had been imported and if the user has modified it
677 // Gradient grab-bag depends on theme grab-bag, which is implemented
678 // only for DOCX.
679 if( EqualGradients( aOriginalGradient, aGradient ) && GetDocumentType() == DOCUMENT_DOCX)
680 {
681 // If we have no gradient stops that means original gradient were defined by a theme.
682 if( aGradientStops.hasElements() )
683 {
684 mpFS->startElementNS(XML_a, XML_gradFill, XML_rotWithShape, "0");
685 WriteGrabBagGradientFill(aGradientStops, aGradient);
686 mpFS->endElementNS( XML_a, XML_gradFill );
687 }
688 }
689 else
690 {
691 awt::Gradient aTransparenceGradient;
692 mpFS->startElementNS(XML_a, XML_gradFill, XML_rotWithShape, "0");
693 OUString sFillTransparenceGradientName;
694 if (GetProperty(rXPropSet, "FillTransparenceGradientName")
695 && (mAny >>= sFillTransparenceGradientName)
696 && !sFillTransparenceGradientName.isEmpty())
697 {
698 if (GetProperty(rXPropSet, "FillTransparenceGradient"))
699 aTransparenceGradient = *o3tl::doAccess<awt::Gradient>(mAny);
700 }
701 else if (GetProperty(rXPropSet, "FillTransparence"))
702 {
703 // currently only StartColor and EndColor are evaluated in WriteGradientFill()
704 sal_Int32 nTransparency = 0;
705 mAny >>= nTransparency;
706 // convert percent to gray color
707 nTransparency = nTransparency * 255/100;
708 const sal_Int32 aGrayColor = static_cast<sal_Int32>( nTransparency | nTransparency << 8 | nTransparency << 16 );
709 aTransparenceGradient.StartColor = aGrayColor;
710 aTransparenceGradient.EndColor = aGrayColor;
711 }
712 WriteGradientFill(aGradient, aTransparenceGradient);
713 mpFS->endElementNS(XML_a, XML_gradFill);
714 }
715}
716
717void DrawingML::WriteGrabBagGradientFill( const Sequence< PropertyValue >& aGradientStops, awt::Gradient rGradient )
718{
719 // write back the original gradient
720 mpFS->startElementNS(XML_a, XML_gsLst);
721
722 // get original stops and write them
723 for( const auto& rGradientStop : aGradientStops )
724 {
725 Sequence< PropertyValue > aGradientStop;
726 rGradientStop.Value >>= aGradientStop;
727
728 // get values
729 OUString sSchemeClr;
730 double nPos = 0;
731 sal_Int16 nTransparency = 0;
732 ::Color nRgbClr;
733 Sequence< PropertyValue > aTransformations;
734 for( const auto& rProp : std::as_const(aGradientStop) )
735 {
736 if( rProp.Name == "SchemeClr" )
737 rProp.Value >>= sSchemeClr;
738 else if( rProp.Name == "RgbClr" )
739 rProp.Value >>= nRgbClr;
740 else if( rProp.Name == "Pos" )
741 rProp.Value >>= nPos;
742 else if( rProp.Name == "Transparency" )
743 rProp.Value >>= nTransparency;
744 else if( rProp.Name == "Transformations" )
745 rProp.Value >>= aTransformations;
746 }
747 // write stop
748 mpFS->startElementNS(XML_a, XML_gs, XML_pos, OString::number(nPos * 100000.0).getStr());
749 if( sSchemeClr.isEmpty() )
750 {
751 // Calculate alpha value (see oox/source/drawingml/color.cxx : getTransparency())
752 sal_Int32 nAlpha = MAX_PERCENT - ( PER_PERCENT * nTransparency );
753 WriteColor( nRgbClr, nAlpha );
754 }
755 else
756 {
757 WriteColor( sSchemeClr, aTransformations );
758 }
759 mpFS->endElementNS( XML_a, XML_gs );
760 }
761 mpFS->endElementNS( XML_a, XML_gsLst );
762
763 switch (rGradient.Style)
764 {
765 default:
766 mpFS->singleElementNS(
767 XML_a, XML_lin, XML_ang,
768 OString::number(((3600 - rGradient.Angle + 900) * 6000) % 21600000));
769 break;
770 case awt::GradientStyle_RADIAL:
771 WriteGradientPath(rGradient, mpFS, true);
772 break;
773 }
774}
775
776void DrawingML::WriteGradientFill(awt::Gradient rGradient, awt::Gradient rTransparenceGradient,
777 const uno::Reference<beans::XPropertySet>& rXPropSet)
778{
779 sal_Int32 nStartAlpha;
780 sal_Int32 nEndAlpha;
781 if( rXPropSet.is() && GetProperty(rXPropSet, "FillTransparence") )
782 {
783 sal_Int32 nTransparency = 0;
784 mAny >>= nTransparency;
785 nStartAlpha = nEndAlpha = (MAX_PERCENT - (PER_PERCENT * nTransparency));
786 }
787 else
788 {
789 nStartAlpha = GetAlphaFromTransparenceGradient(rTransparenceGradient, true);
790 nEndAlpha = GetAlphaFromTransparenceGradient(rTransparenceGradient, false);
791 }
792 switch( rGradient.Style )
793 {
794 default:
795 case awt::GradientStyle_LINEAR:
796 {
797 mpFS->startElementNS(XML_a, XML_gsLst);
798 WriteGradientStop(rGradient.Border, ColorWithIntensity(rGradient.StartColor, rGradient.StartIntensity),
799 nStartAlpha);
800 WriteGradientStop(100, ColorWithIntensity(rGradient.EndColor, rGradient.EndIntensity),
801 nEndAlpha);
802 mpFS->endElementNS( XML_a, XML_gsLst );
803 mpFS->singleElementNS(
804 XML_a, XML_lin, XML_ang,
805 OString::number(((3600 - rGradient.Angle + 900) * 6000) % 21600000));
806 break;
807 }
808
809 case awt::GradientStyle_AXIAL:
810 {
811 mpFS->startElementNS(XML_a, XML_gsLst);
812 WriteGradientStop(0, ColorWithIntensity(rGradient.EndColor, rGradient.EndIntensity),
813 nEndAlpha);
814 if (rGradient.Border > 0 && rGradient.Border < 100)
815 {
816 WriteGradientStop(rGradient.Border/2,
817 ColorWithIntensity(rGradient.EndColor, rGradient.EndIntensity),
818 nEndAlpha);
819 }
820 WriteGradientStop(50, ColorWithIntensity(rGradient.StartColor, rGradient.StartIntensity),
821 nStartAlpha);
822 if (rGradient.Border > 0 && rGradient.Border < 100)
823 {
824 WriteGradientStop(100 - rGradient.Border/2,
825 ColorWithIntensity(rGradient.EndColor, rGradient.EndIntensity),
826 nEndAlpha);
827 }
828 WriteGradientStop(100, ColorWithIntensity(rGradient.EndColor, rGradient.EndIntensity),
829 nEndAlpha);
830 mpFS->endElementNS(XML_a, XML_gsLst);
831 mpFS->singleElementNS(
832 XML_a, XML_lin, XML_ang,
833 OString::number(((3600 - rGradient.Angle + 900) * 6000) % 21600000));
834 break;
835 }
836
837 case awt::GradientStyle_RADIAL:
838 case awt::GradientStyle_ELLIPTICAL:
839 case awt::GradientStyle_RECT:
840 case awt::GradientStyle_SQUARE:
841 {
842 mpFS->startElementNS(XML_a, XML_gsLst);
843 WriteGradientStop(0, ColorWithIntensity(rGradient.EndColor, rGradient.EndIntensity),
844 nEndAlpha);
845 if (rGradient.Border > 0 && rGradient.Border < 100)
846 {
847 // Map border to an additional gradient stop, which has the
848 // same color as the final stop.
849 WriteGradientStop(100 - rGradient.Border,
850 ColorWithIntensity(rGradient.StartColor, rGradient.StartIntensity),
851 nStartAlpha);
852 }
854 ColorWithIntensity(rGradient.StartColor, rGradient.StartIntensity),
855 nStartAlpha);
856 mpFS->endElementNS(XML_a, XML_gsLst);
857
858 WriteGradientPath(rGradient, mpFS, rGradient.Style == awt::GradientStyle_RADIAL || rGradient.Style == awt::GradientStyle_ELLIPTICAL);
859 break;
860 }
861 }
862}
863
864void DrawingML::WriteLineArrow( const Reference< XPropertySet >& rXPropSet, bool bLineStart )
865{
866 ESCHER_LineEnd eLineEnd;
867 sal_Int32 nArrowLength;
868 sal_Int32 nArrowWidth;
869
870 if ( !EscherPropertyContainer::GetLineArrow( bLineStart, rXPropSet, eLineEnd, nArrowLength, nArrowWidth ) )
871 return;
872
873 const char* len;
874 const char* type;
875 const char* width;
876
877 switch( nArrowLength )
878 {
880 len = "sm";
881 break;
882 default:
884 len = "med";
885 break;
887 len = "lg";
888 break;
889 }
890
891 switch( eLineEnd )
892 {
893 default:
894 case ESCHER_LineNoEnd:
895 type = "none";
896 break;
898 type = "triangle";
899 break;
901 type = "stealth";
902 break;
904 type = "diamond";
905 break;
907 type = "oval";
908 break;
910 type = "arrow";
911 break;
912 }
913
914 switch( nArrowWidth )
915 {
917 width = "sm";
918 break;
919 default:
921 width = "med";
922 break;
924 width = "lg";
925 break;
926 }
927
928 mpFS->singleElementNS( XML_a, bLineStart ? XML_headEnd : XML_tailEnd,
929 XML_len, len,
930 XML_type, type,
931 XML_w, width );
932}
933
935{
936 drawing::LineStyle aLineStyle( drawing::LineStyle_NONE );
937 if (GetProperty(rXPropSet, "LineStyle"))
938 mAny >>= aLineStyle;
939
940 const LineCap aLineCap = GetProperty(rXPropSet, "LineCap") ? mAny.get<drawing::LineCap>() : LineCap_BUTT;
941
942 sal_uInt32 nLineWidth = 0;
943 sal_uInt32 nEmuLineWidth = 0;
944 ::Color nColor;
945 sal_Int32 nColorAlpha = MAX_PERCENT;
946 bool bColorSet = false;
947 const char* cap = nullptr;
948 drawing::LineDash aLineDash;
949 bool bDashSet = false;
950 bool bNoFill = false;
951
952
953 // get InteropGrabBag and search the relevant attributes
954 OUString sColorFillScheme;
955 ::Color aResolvedColorFillScheme;
956
957 ::Color nOriginalColor;
958 ::Color nStyleColor;
959 sal_uInt32 nStyleLineWidth = 0;
960
961 Sequence<PropertyValue> aStyleProperties;
962 Sequence<PropertyValue> aTransformations;
963
964 drawing::LineStyle aStyleLineStyle(drawing::LineStyle_NONE);
965 drawing::LineJoint aStyleLineJoint(drawing::LineJoint_NONE);
966
967 if (GetProperty(rXPropSet, "InteropGrabBag"))
968 {
970 mAny >>= aGrabBag;
971
972 for (const auto& rProp : std::as_const(aGrabBag))
973 {
974 if( rProp.Name == "SpPrLnSolidFillSchemeClr" )
975 rProp.Value >>= sColorFillScheme;
976 if( rProp.Name == "SpPrLnSolidFillResolvedSchemeClr" )
977 rProp.Value >>= aResolvedColorFillScheme;
978 else if( rProp.Name == "OriginalLnSolidFillClr" )
979 rProp.Value >>= nOriginalColor;
980 else if( rProp.Name == "StyleLnRef" )
981 rProp.Value >>= aStyleProperties;
982 else if( rProp.Name == "SpPrLnSolidFillSchemeClrTransformations" )
983 rProp.Value >>= aTransformations;
984 else if( rProp.Name == "EmuLineWidth" )
985 rProp.Value >>= nEmuLineWidth;
986 }
987 for (const auto& rStyleProp : std::as_const(aStyleProperties))
988 {
989 if( rStyleProp.Name == "Color" )
990 rStyleProp.Value >>= nStyleColor;
991 else if( rStyleProp.Name == "LineStyle" )
992 rStyleProp.Value >>= aStyleLineStyle;
993 else if( rStyleProp.Name == "LineJoint" )
994 rStyleProp.Value >>= aStyleLineJoint;
995 else if( rStyleProp.Name == "LineWidth" )
996 rStyleProp.Value >>= nStyleLineWidth;
997 }
998 }
999
1000 if (GetProperty(rXPropSet, "LineWidth"))
1001 mAny >>= nLineWidth;
1002
1003 switch (aLineStyle)
1004 {
1005 case drawing::LineStyle_NONE:
1006 bNoFill = true;
1007 break;
1008 case drawing::LineStyle_DASH:
1009 if (GetProperty(rXPropSet, "LineDash"))
1010 {
1011 aLineDash = mAny.get<drawing::LineDash>();
1012 //this query is good for shapes, but in the case of charts it returns 0 values
1013 if (aLineDash.Dots == 0 && aLineDash.DotLen == 0 && aLineDash.Dashes == 0 && aLineDash.DashLen == 0 && aLineDash.Distance == 0) {
1014 OUString aLineDashName;
1015 if (GetProperty(rXPropSet, "LineDashName"))
1016 mAny >>= aLineDashName;
1017 if (!aLineDashName.isEmpty() && xModel) {
1018 css::uno::Any aAny = getLineDash(xModel, aLineDashName);
1019 aAny >>= aLineDash;
1020 }
1021 }
1022 }
1023 else
1024 {
1025 //export the linestyle of chart wall (plot area) and chart page
1026 OUString aLineDashName;
1027 if (GetProperty(rXPropSet, "LineDashName"))
1028 mAny >>= aLineDashName;
1029 if (!aLineDashName.isEmpty() && xModel) {
1030 css::uno::Any aAny = getLineDash(xModel, aLineDashName);
1031 aAny >>= aLineDash;
1032 }
1033 }
1034 bDashSet = true;
1035 if (aLineDash.Style == DashStyle_ROUND || aLineDash.Style == DashStyle_ROUNDRELATIVE)
1036 {
1037 cap = "rnd";
1038 }
1039
1040 SAL_INFO("oox.shape", "dash dots: " << aLineDash.Dots << " dashes: " << aLineDash.Dashes
1041 << " dotlen: " << aLineDash.DotLen << " dashlen: " << aLineDash.DashLen << " distance: " << aLineDash.Distance);
1042
1043 [[fallthrough]];
1044 case drawing::LineStyle_SOLID:
1045 default:
1046 if (GetProperty(rXPropSet, "LineColor"))
1047 {
1048 nColor = ::Color(ColorTransparency, mAny.get<sal_uInt32>() & 0xffffff);
1049 bColorSet = true;
1050 }
1051 if (GetProperty(rXPropSet, "LineTransparence"))
1052 {
1053 nColorAlpha = MAX_PERCENT - (mAny.get<sal_Int16>() * PER_PERCENT);
1054 }
1055 if (aLineCap == LineCap_ROUND)
1056 cap = "rnd";
1057 else if (aLineCap == LineCap_SQUARE)
1058 cap = "sq";
1059 break;
1060 }
1061
1062 // if the line-width was not modified after importing then the original EMU value will be exported to avoid unexpected conversion (rounding) error
1063 if (nEmuLineWidth == 0 || static_cast<sal_uInt32>(oox::drawingml::convertEmuToHmm(nEmuLineWidth)) != nLineWidth)
1065 mpFS->startElementNS( XML_a, XML_ln,
1066 XML_cap, cap,
1067 XML_w, sax_fastparser::UseIf(OString::number(nEmuLineWidth),
1068 nLineWidth == 0 || (nLineWidth > 1 && nStyleLineWidth != nLineWidth)) );
1069
1070 if( bColorSet )
1071 {
1072 if( nColor != nOriginalColor )
1073 {
1074 // the user has set a different color for the line
1075 WriteSolidFill( nColor, nColorAlpha );
1076 }
1077 else if( !sColorFillScheme.isEmpty() )
1078 {
1079 // the line had a scheme color and the user didn't change it
1080 WriteSolidFill( aResolvedColorFillScheme, aTransformations );
1081 }
1082 else
1083 {
1084 WriteSolidFill( nColor, nColorAlpha );
1085 }
1086 }
1087
1088 if( bDashSet && aStyleLineStyle != drawing::LineStyle_DASH )
1089 {
1090 // Try to detect if it might come from ms preset line style import.
1091 // MS Office styles are always relative, both binary and OOXML.
1092 // "dot" is always the first dash and "dash" the second one. All OOXML presets linestyles
1093 // start with the longer one. Definitions are in OOXML part 1, 20.1.10.49
1094 // The tests are strict, for to not catch styles from standard.sod (as of Aug 2019).
1095 bool bIsConverted = false;
1096
1097 bool bIsRelative(aLineDash.Style == DashStyle_RECTRELATIVE || aLineDash.Style == DashStyle_ROUNDRELATIVE);
1098 if ( bIsRelative && aLineDash.Dots == 1)
1099 { // The length were tweaked on import in case of prstDash. Revert it here.
1100 sal_uInt32 nDotLen = aLineDash.DotLen;
1101 sal_uInt32 nDashLen = aLineDash.DashLen;
1102 sal_uInt32 nDistance = aLineDash.Distance;
1103 if (aLineCap != LineCap_BUTT && nDistance >= 99)
1104 {
1105 nDistance -= 99;
1106 nDotLen += 99;
1107 if (nDashLen > 0)
1108 nDashLen += 99;
1109 }
1110 // LO uses length 0 for 100%, if the attribute is missing in ODF.
1111 // Other applications might write 100%. Make is unique for the conditions.
1112 if (nDotLen == 0)
1113 nDotLen = 100;
1114 if (nDashLen == 0 && aLineDash.Dashes > 0)
1115 nDashLen = 100;
1116 bIsConverted = true;
1117 if (nDotLen == 100 && aLineDash.Dashes == 0 && nDashLen == 0 && nDistance == 300)
1118 {
1119 mpFS->singleElementNS(XML_a, XML_prstDash, XML_val, "dot");
1120 }
1121 else if (nDotLen == 400 && aLineDash.Dashes == 0 && nDashLen == 0 && nDistance == 300)
1122 {
1123 mpFS->singleElementNS(XML_a, XML_prstDash, XML_val, "dash");
1124 }
1125 else if (nDotLen == 400 && aLineDash.Dashes == 1 && nDashLen == 100 && nDistance == 300)
1126 {
1127 mpFS->singleElementNS(XML_a, XML_prstDash, XML_val, "dashDot");
1128 }
1129 else if (nDotLen == 800 && aLineDash.Dashes == 0 && nDashLen == 0 && nDistance == 300)
1130 {
1131 mpFS->singleElementNS(XML_a, XML_prstDash, XML_val, "lgDash");
1132 }
1133 else if (nDotLen == 800 && aLineDash.Dashes == 1 && nDashLen == 100 && nDistance == 300)
1134 {
1135 mpFS->singleElementNS(XML_a, XML_prstDash, XML_val, "lgDashDot");
1136 }
1137 else if (nDotLen == 800 && aLineDash.Dashes == 2 && nDashLen == 100 && nDistance == 300)
1138 {
1139 mpFS->singleElementNS(XML_a, XML_prstDash, XML_val, "lgDashDotDot");
1140 }
1141 else if (nDotLen == 100 && aLineDash.Dashes == 0 && nDashLen == 0 && nDistance == 100)
1142 {
1143 mpFS->singleElementNS(XML_a, XML_prstDash, XML_val, "sysDot");
1144 }
1145 else if (nDotLen == 300 && aLineDash.Dashes == 0 && nDashLen == 0 && nDistance == 100)
1146 {
1147 mpFS->singleElementNS(XML_a, XML_prstDash, XML_val, "sysDash");
1148 }
1149 else if (nDotLen == 300 && aLineDash.Dashes == 1 && nDashLen == 100 && nDistance == 100)
1150 {
1151 mpFS->singleElementNS(XML_a, XML_prstDash, XML_val, "sysDashDot");
1152 }
1153 else if (nDotLen == 300 && aLineDash.Dashes == 2 && nDashLen == 100 && nDistance == 100)
1154 {
1155 mpFS->singleElementNS(XML_a, XML_prstDash, XML_val, "sysDashDotDot");
1156 }
1157 else
1158 bIsConverted = false;
1159 }
1160 // Do not map our own line styles to OOXML prstDash values, because custDash gives better results.
1161 if (!bIsConverted)
1162 {
1163 mpFS->startElementNS(XML_a, XML_custDash);
1164 // In case of hairline we would need the current pixel size. Instead use a reasonable
1165 // ersatz for it. The value is the same as SMALLEST_DASH_WIDTH in xattr.cxx.
1166 // (And it makes sure fLineWidth is not zero in below division.)
1167 double fLineWidth = nLineWidth > 0 ? nLineWidth : 26.95;
1168 int i;
1169 double fSp = bIsRelative ? aLineDash.Distance : aLineDash.Distance * 100.0 / fLineWidth;
1170 // LO uses line width, in case Distance is zero. MS Office would use a space of zero length.
1171 // So set 100% explicitly.
1172 if (aLineDash.Distance <= 0)
1173 fSp = 100.0;
1174 // In case of custDash, round caps are included in dash length in MS Office. Square caps are added
1175 // to dash length, same as in ODF. Change the length values accordingly.
1176 if (aLineCap == LineCap_ROUND && fSp > 99.0)
1177 fSp -= 99.0;
1178
1179 if (aLineDash.Dots > 0)
1180 {
1181 double fD = bIsRelative ? aLineDash.DotLen : aLineDash.DotLen * 100.0 / fLineWidth;
1182 // LO sets length to 0, if attribute is missing in ODF. Then a relative length of 100% is intended.
1183 if (aLineDash.DotLen == 0)
1184 fD = 100.0;
1185 // Tweak dash length, see above.
1186 if (aLineCap == LineCap_ROUND && fSp > 99.0)
1187 fD += 99.0;
1188
1189 for( i = 0; i < aLineDash.Dots; i ++ )
1190 {
1191 mpFS->singleElementNS( XML_a, XML_ds,
1192 XML_d , write1000thOfAPercent(fD),
1193 XML_sp, write1000thOfAPercent(fSp) );
1194 }
1195 }
1196 if ( aLineDash.Dashes > 0 )
1197 {
1198 double fD = bIsRelative ? aLineDash.DashLen : aLineDash.DashLen * 100.0 / fLineWidth;
1199 // LO sets length to 0, if attribute is missing in ODF. Then a relative length of 100% is intended.
1200 if (aLineDash.DashLen == 0)
1201 fD = 100.0;
1202 // Tweak dash length, see above.
1203 if (aLineCap == LineCap_ROUND && fSp > 99.0)
1204 fD += 99.0;
1205
1206 for( i = 0; i < aLineDash.Dashes; i ++ )
1207 {
1208 mpFS->singleElementNS( XML_a , XML_ds,
1209 XML_d , write1000thOfAPercent(fD),
1210 XML_sp, write1000thOfAPercent(fSp) );
1211 }
1212 }
1213
1215 "oox.shape", "while writing outline - custom dash - line width was < 0 : " << nLineWidth);
1216 SAL_WARN_IF(aLineDash.Dashes < 0,
1217 "oox.shape", "while writing outline - custom dash - number of dashes was < 0 : " << aLineDash.Dashes);
1218 SAL_WARN_IF(aLineDash.Dashes > 0 && aLineDash.DashLen <= 0,
1219 "oox.shape", "while writing outline - custom dash - dash length was < 0 : " << aLineDash.DashLen);
1220 SAL_WARN_IF(aLineDash.Dots < 0,
1221 "oox.shape", "while writing outline - custom dash - number of dots was < 0 : " << aLineDash.Dots);
1222 SAL_WARN_IF(aLineDash.Dots > 0 && aLineDash.DotLen <= 0,
1223 "oox.shape", "while writing outline - custom dash - dot length was < 0 : " << aLineDash.DotLen);
1224 SAL_WARN_IF(aLineDash.Distance <= 0,
1225 "oox.shape", "while writing outline - custom dash - distance was < 0 : " << aLineDash.Distance);
1226
1227 mpFS->endElementNS( XML_a, XML_custDash );
1228 }
1229 }
1230
1231 if (!bNoFill && nLineWidth > 1 && GetProperty(rXPropSet, "LineJoint"))
1232 {
1233 LineJoint eLineJoint = mAny.get<LineJoint>();
1234
1235 if( aStyleLineJoint == LineJoint_NONE || aStyleLineJoint != eLineJoint )
1236 {
1237 // style-defined line joint does not exist, or is different from the shape's joint
1238 switch( eLineJoint )
1239 {
1240 case LineJoint_NONE:
1241 case LineJoint_BEVEL:
1242 mpFS->singleElementNS(XML_a, XML_bevel);
1243 break;
1244 default:
1245 case LineJoint_MIDDLE:
1246 case LineJoint_MITER:
1247 mpFS->singleElementNS(XML_a, XML_miter);
1248 break;
1249 case LineJoint_ROUND:
1250 mpFS->singleElementNS(XML_a, XML_round);
1251 break;
1252 }
1253 }
1254 }
1255
1256 if( !bNoFill )
1257 {
1258 WriteLineArrow( rXPropSet, true );
1259 WriteLineArrow( rXPropSet, false );
1260 }
1261 else
1262 {
1263 mpFS->singleElementNS(XML_a, XML_noFill);
1264 }
1265
1266 mpFS->endElementNS( XML_a, XML_ln );
1267}
1268
1270{
1271 switch ( meDocumentType )
1272 {
1273 case DOCUMENT_DOCX: return "word";
1274 case DOCUMENT_PPTX: return "ppt";
1275 case DOCUMENT_XLSX: return "xl";
1276 }
1277
1278 return "unknown";
1279}
1280
1282{
1283 switch ( meDocumentType )
1284 {
1285 case DOCUMENT_DOCX: return "";
1286 case DOCUMENT_PPTX:
1287 case DOCUMENT_XLSX: return "../";
1288 }
1289
1290 return "unknown";
1291}
1292
1293OUString DrawingML::WriteImage( const Graphic& rGraphic , bool bRelPathToMedia )
1294{
1295 GfxLink aLink = rGraphic.GetGfxLink ();
1296 BitmapChecksum aChecksum = rGraphic.GetChecksum();
1297 OUString sMediaType;
1298 const char* pExtension = "";
1299 OUString sRelId;
1300 OUString sPath;
1301
1302 // tdf#74670 tdf#91286 Save image only once
1303 if (!maExportGraphics.empty())
1304 {
1305 auto aIterator = maExportGraphics.top().find(aChecksum);
1306 if (aIterator != maExportGraphics.top().end())
1307 sPath = aIterator->second;
1308 }
1309
1310 if (sPath.isEmpty())
1311 {
1312 SvMemoryStream aStream;
1313 const void* aData = aLink.GetData();
1314 std::size_t nDataSize = aLink.GetDataSize();
1315
1316 switch (aLink.GetType())
1317 {
1318 case GfxLinkType::NativeGif:
1319 sMediaType = "image/gif";
1320 pExtension = ".gif";
1321 break;
1322
1323 // #i15508# added BMP type for better exports
1324 // export not yet active, so adding for reference (not checked)
1325 case GfxLinkType::NativeBmp:
1326 sMediaType = "image/bmp";
1327 pExtension = ".bmp";
1328 break;
1329
1330 case GfxLinkType::NativeJpg:
1331 sMediaType = "image/jpeg";
1332 pExtension = ".jpeg";
1333 break;
1334 case GfxLinkType::NativePng:
1335 sMediaType = "image/png";
1336 pExtension = ".png";
1337 break;
1338 case GfxLinkType::NativeTif:
1339 sMediaType = "image/tiff";
1340 pExtension = ".tif";
1341 break;
1342 case GfxLinkType::NativeWmf:
1343 sMediaType = "image/x-wmf";
1344 pExtension = ".wmf";
1345 break;
1346 case GfxLinkType::NativeMet:
1347 sMediaType = "image/x-met";
1348 pExtension = ".met";
1349 break;
1350 case GfxLinkType::NativePct:
1351 sMediaType = "image/x-pict";
1352 pExtension = ".pct";
1353 break;
1354 case GfxLinkType::NativeMov:
1355 sMediaType = "application/movie";
1356 pExtension = ".MOV";
1357 break;
1358 default:
1359 {
1360 GraphicType aType = rGraphic.GetType();
1361 if (aType == GraphicType::Bitmap || aType == GraphicType::GdiMetafile)
1362 {
1363 if (aType == GraphicType::Bitmap)
1364 {
1365 (void)GraphicConverter::Export(aStream, rGraphic, ConvertDataFormat::PNG);
1366 sMediaType = "image/png";
1367 pExtension = ".png";
1368 }
1369 else
1370 {
1371 (void)GraphicConverter::Export(aStream, rGraphic, ConvertDataFormat::EMF);
1372 sMediaType = "image/x-emf";
1373 pExtension = ".emf";
1374 }
1375 }
1376 else
1377 {
1378 SAL_WARN("oox.shape", "unhandled graphic type " << static_cast<int>(aType));
1379 /*Earlier, even in case of unhandled graphic types we were
1380 proceeding to write the image, which would eventually
1381 write an empty image with a zero size, and return a valid
1382 relationID, which is incorrect.
1383 */
1384 return sRelId;
1385 }
1386
1387 aData = aStream.GetData();
1388 nDataSize = aStream.GetEndOfData();
1389 break;
1390 }
1391 }
1392
1394 OUStringBuffer()
1395 .appendAscii(GetComponentDir())
1396 .append("/media/image" + OUString::number(mnImageCounter))
1397 .appendAscii(pExtension)
1398 .makeStringAndClear(),
1399 sMediaType);
1400 xOutStream->writeBytes(Sequence<sal_Int8>(static_cast<const sal_Int8*>(aData), nDataSize));
1401 xOutStream->closeOutput();
1402
1403 const OString sRelPathToMedia = "media/image";
1404 OString sRelationCompPrefix;
1405 if (bRelPathToMedia)
1406 sRelationCompPrefix = "../";
1407 else
1408 sRelationCompPrefix = GetRelationCompPrefix();
1409 sPath = OUStringBuffer()
1410 .appendAscii(sRelationCompPrefix.getStr())
1411 .appendAscii(sRelPathToMedia.getStr())
1412 .append(static_cast<sal_Int32>(mnImageCounter++))
1413 .appendAscii(pExtension)
1414 .makeStringAndClear();
1415
1416 if (!maExportGraphics.empty())
1417 maExportGraphics.top()[aChecksum] = sPath;
1418 }
1419
1420 sRelId = mpFB->addRelation( mpFS->getOutputStream(),
1422 sPath );
1423
1424 return sRelId;
1425}
1426
1427void DrawingML::WriteMediaNonVisualProperties(const css::uno::Reference<css::drawing::XShape>& xShape)
1428{
1429 SdrMediaObj* pMediaObj = dynamic_cast<SdrMediaObj*>(SdrObject::getSdrObjectFromXShape(xShape));
1430 if (!pMediaObj)
1431 return;
1432
1433 // extension
1434 OUString aExtension;
1435 const OUString& rURL(pMediaObj->getURL());
1436 int nLastDot = rURL.lastIndexOf('.');
1437 if (nLastDot >= 0)
1438 aExtension = rURL.copy(nLastDot);
1439
1440 bool bEmbed = rURL.startsWith("vnd.sun.star.Package:");
1441 Relationship eMediaType = Relationship::VIDEO;
1442
1443 // mime type
1444#if HAVE_FEATURE_AVMEDIA
1445 OUString aMimeType(pMediaObj->getMediaProperties().getMimeType());
1446#else
1447 OUString aMimeType("none");
1448#endif
1449 if (aMimeType == "application/vnd.sun.star.media")
1450 {
1451 // try to set something better
1452 // TODO fix the importer to actually set the mimetype on import
1453 if (aExtension.equalsIgnoreAsciiCase(".avi"))
1454 aMimeType = "video/x-msvideo";
1455 else if (aExtension.equalsIgnoreAsciiCase(".flv"))
1456 aMimeType = "video/x-flv";
1457 else if (aExtension.equalsIgnoreAsciiCase(".mp4"))
1458 aMimeType = "video/mp4";
1459 else if (aExtension.equalsIgnoreAsciiCase(".mov"))
1460 aMimeType = "video/quicktime";
1461 else if (aExtension.equalsIgnoreAsciiCase(".ogv"))
1462 aMimeType = "video/ogg";
1463 else if (aExtension.equalsIgnoreAsciiCase(".wmv"))
1464 aMimeType = "video/x-ms-wmv";
1465 else if (aExtension.equalsIgnoreAsciiCase(".wav"))
1466 {
1467 aMimeType = "audio/x-wav";
1468 eMediaType = Relationship::AUDIO;
1469 }
1470 else if (aExtension.equalsIgnoreAsciiCase(".m4a"))
1471 {
1472 aMimeType = "audio/mp4";
1473 eMediaType = Relationship::AUDIO;
1474 }
1475 else if (aExtension.equalsIgnoreAsciiCase(".mp3"))
1476 {
1477 aMimeType = "audio/mp3";
1478 eMediaType = Relationship::AUDIO;
1479 }
1480 }
1481
1482 OUString aVideoFileRelId;
1483 OUString aMediaRelId;
1484
1485 if (bEmbed)
1486 {
1487 // copy the video stream
1488 Reference<XOutputStream> xOutStream = mpFB->openFragmentStream(OUStringBuffer()
1489 .appendAscii(GetComponentDir())
1490 .append("/media/media" +
1491 OUString::number(mnImageCounter) +
1492 aExtension)
1493 .makeStringAndClear(),
1494 aMimeType);
1495
1496 uno::Reference<io::XInputStream> xInputStream(pMediaObj->GetInputStream());
1497 comphelper::OStorageHelper::CopyInputToOutput(xInputStream, xOutStream);
1498
1499 xOutStream->closeOutput();
1500
1501 // create the relation
1502 OUString aPath = OUStringBuffer().appendAscii(GetRelationCompPrefix())
1503 .append("media/media" + OUString::number(mnImageCounter++) + aExtension)
1504 .makeStringAndClear();
1505 aVideoFileRelId = mpFB->addRelation(mpFS->getOutputStream(), oox::getRelationship(eMediaType), aPath);
1506 aMediaRelId = mpFB->addRelation(mpFS->getOutputStream(), oox::getRelationship(Relationship::MEDIA), aPath);
1507 }
1508 else
1509 {
1510 aVideoFileRelId = mpFB->addRelation(mpFS->getOutputStream(), oox::getRelationship(eMediaType), rURL, true);
1511 aMediaRelId = mpFB->addRelation(mpFS->getOutputStream(), oox::getRelationship(Relationship::MEDIA), rURL, true);
1512 }
1513
1514 GetFS()->startElementNS(XML_p, XML_nvPr);
1515
1516 GetFS()->singleElementNS(XML_a, eMediaType == Relationship::VIDEO ? XML_videoFile : XML_audioFile,
1517 FSNS(XML_r, XML_link), aVideoFileRelId);
1518
1519 GetFS()->startElementNS(XML_p, XML_extLst);
1520 // media extensions; google this ID for details
1521 GetFS()->startElementNS(XML_p, XML_ext, XML_uri, "{DAA4B4D4-6D71-4841-9C94-3DE7FCFB9230}");
1522
1523 GetFS()->singleElementNS(XML_p14, XML_media,
1524 bEmbed? FSNS(XML_r, XML_embed): FSNS(XML_r, XML_link), aMediaRelId);
1525
1526 GetFS()->endElementNS(XML_p, XML_ext);
1527 GetFS()->endElementNS(XML_p, XML_extLst);
1528
1529 GetFS()->endElementNS(XML_p, XML_nvPr);
1530}
1531
1532void DrawingML::WriteImageBrightnessContrastTransparence(uno::Reference<beans::XPropertySet> const & rXPropSet)
1533{
1534 sal_Int16 nBright = 0;
1535 sal_Int32 nContrast = 0;
1536 sal_Int32 nTransparence = 0;
1537
1538 if (GetProperty(rXPropSet, "AdjustLuminance"))
1539 nBright = mAny.get<sal_Int16>();
1540 if (GetProperty(rXPropSet, "AdjustContrast"))
1541 nContrast = mAny.get<sal_Int32>();
1542 // Used for shapes with picture fill
1543 if (GetProperty(rXPropSet, "FillTransparence"))
1544 nTransparence = mAny.get<sal_Int32>();
1545 // Used for pictures
1546 if (nTransparence == 0 && GetProperty(rXPropSet, "Transparency"))
1547 nTransparence = static_cast<sal_Int32>(mAny.get<sal_Int16>());
1548
1549 if (GetProperty(rXPropSet, "GraphicColorMode"))
1550 {
1551 drawing::ColorMode aColorMode;
1552 mAny >>= aColorMode;
1553 if (aColorMode == drawing::ColorMode_GREYS)
1554 mpFS->singleElementNS(XML_a, XML_grayscl);
1555 else if (aColorMode == drawing::ColorMode_MONO)
1556 //black/white has a 0,5 threshold in LibreOffice
1557 mpFS->singleElementNS(XML_a, XML_biLevel, XML_thresh, OString::number(50000));
1558 else if (aColorMode == drawing::ColorMode_WATERMARK)
1559 {
1560 //map watermark with mso washout
1561 nBright = 70;
1562 nContrast = -70;
1563 }
1564 }
1565
1566
1567 if (nBright || nContrast)
1568 {
1569 mpFS->singleElementNS(XML_a, XML_lum,
1570 XML_bright, sax_fastparser::UseIf(OString::number(nBright * 1000), nBright != 0),
1571 XML_contrast, sax_fastparser::UseIf(OString::number(nContrast * 1000), nContrast != 0));
1572 }
1573
1574 if (nTransparence)
1575 {
1576 sal_Int32 nAlphaMod = (100 - nTransparence ) * PER_PERCENT;
1577 mpFS->singleElementNS(XML_a, XML_alphaModFix, XML_amt, OString::number(nAlphaMod));
1578 }
1579}
1580
1581OUString DrawingML::WriteXGraphicBlip(uno::Reference<beans::XPropertySet> const & rXPropSet,
1582 uno::Reference<graphic::XGraphic> const & rxGraphic,
1583 bool bRelPathToMedia)
1584{
1585 OUString sRelId;
1586
1587 if (!rxGraphic.is())
1588 return sRelId;
1589
1590 Graphic aGraphic(rxGraphic);
1591 sRelId = WriteImage(aGraphic, bRelPathToMedia);
1592
1593 mpFS->startElementNS(XML_a, XML_blip, FSNS(XML_r, XML_embed), sRelId);
1594
1596
1597 WriteArtisticEffect(rXPropSet);
1598
1599 mpFS->endElementNS(XML_a, XML_blip);
1600
1601 return sRelId;
1602}
1603
1604void DrawingML::WriteXGraphicBlipMode(uno::Reference<beans::XPropertySet> const & rXPropSet,
1605 uno::Reference<graphic::XGraphic> const & rxGraphic)
1606{
1607 BitmapMode eBitmapMode(BitmapMode_NO_REPEAT);
1608 if (GetProperty(rXPropSet, "FillBitmapMode"))
1609 mAny >>= eBitmapMode;
1610
1611 SAL_INFO("oox.shape", "fill bitmap mode: " << int(eBitmapMode));
1612
1613 switch (eBitmapMode)
1614 {
1615 case BitmapMode_REPEAT:
1616 mpFS->singleElementNS(XML_a, XML_tile);
1617 break;
1618 case BitmapMode_STRETCH:
1619 WriteXGraphicStretch(rXPropSet, rxGraphic);
1620 break;
1621 default:
1622 break;
1623 }
1624}
1625
1626void DrawingML::WriteBlipOrNormalFill( const Reference< XPropertySet >& xPropSet, const OUString& rURLPropName )
1627{
1628 // check for blip and otherwise fall back to normal fill
1629 // we always store normal fill properties but OOXML
1630 // uses a choice between our fill props and BlipFill
1631 if (GetProperty ( xPropSet, rURLPropName ))
1632 WriteBlipFill( xPropSet, rURLPropName );
1633 else
1634 WriteFill(xPropSet);
1635}
1636
1637void DrawingML::WriteBlipFill( const Reference< XPropertySet >& rXPropSet, const OUString& sURLPropName )
1638{
1639 WriteBlipFill( rXPropSet, sURLPropName, XML_a );
1640}
1641
1642void DrawingML::WriteBlipFill( const Reference< XPropertySet >& rXPropSet, const OUString& sURLPropName, sal_Int32 nXmlNamespace )
1643{
1644 if ( !GetProperty( rXPropSet, sURLPropName ) )
1645 return;
1646
1647 uno::Reference<graphic::XGraphic> xGraphic;
1648 if (mAny.has<uno::Reference<awt::XBitmap>>())
1649 {
1650 uno::Reference<awt::XBitmap> xBitmap = mAny.get<uno::Reference<awt::XBitmap>>();
1651 if (xBitmap.is())
1652 xGraphic.set(xBitmap, uno::UNO_QUERY);
1653 }
1654 else if (mAny.has<uno::Reference<graphic::XGraphic>>())
1655 {
1656 xGraphic = mAny.get<uno::Reference<graphic::XGraphic>>();
1657 }
1658
1659 if (xGraphic.is())
1660 {
1661 bool bWriteMode = false;
1662 if (sURLPropName == "FillBitmap" || sURLPropName == "BackGraphic")
1663 bWriteMode = true;
1664 WriteXGraphicBlipFill(rXPropSet, xGraphic, nXmlNamespace, bWriteMode);
1665 }
1666}
1667
1668void DrawingML::WriteXGraphicBlipFill(uno::Reference<beans::XPropertySet> const & rXPropSet,
1669 uno::Reference<graphic::XGraphic> const & rxGraphic,
1670 sal_Int32 nXmlNamespace, bool bWriteMode, bool bRelPathToMedia)
1671{
1672 if (!rxGraphic.is() )
1673 return;
1674
1675 mpFS->startElementNS(nXmlNamespace , XML_blipFill, XML_rotWithShape, "0");
1676
1677 WriteXGraphicBlip(rXPropSet, rxGraphic, bRelPathToMedia);
1678
1680 {
1681 // Write the crop rectangle of Impress as a source rectangle.
1682 WriteSrcRectXGraphic(rXPropSet, rxGraphic);
1683 }
1684
1685 if (bWriteMode)
1686 {
1687 WriteXGraphicBlipMode(rXPropSet, rxGraphic);
1688 }
1689 else if(GetProperty(rXPropSet, "FillBitmapStretch"))
1690 {
1691 bool bStretch = mAny.get<bool>();
1692
1693 if (bStretch)
1694 {
1695 WriteXGraphicStretch(rXPropSet, rxGraphic);
1696 }
1697 }
1698 mpFS->endElementNS(nXmlNamespace, XML_blipFill);
1699}
1700
1702{
1703 if ( GetProperty( rXPropSet, "FillHatch" ) )
1704 {
1705 drawing::Hatch aHatch;
1706 mAny >>= aHatch;
1707 WritePattFill(rXPropSet, aHatch);
1708 }
1709}
1710
1711void DrawingML::WritePattFill(const Reference<XPropertySet>& rXPropSet, const css::drawing::Hatch& rHatch)
1712{
1713 mpFS->startElementNS(XML_a, XML_pattFill, XML_prst, GetHatchPattern(rHatch));
1714
1715 sal_Int32 nAlpha = MAX_PERCENT;
1716 if (GetProperty(rXPropSet, "FillTransparence"))
1717 {
1718 sal_Int32 nTransparency = 0;
1719 mAny >>= nTransparency;
1720 nAlpha = (MAX_PERCENT - (PER_PERCENT * nTransparency));
1721 }
1722
1723 mpFS->startElementNS(XML_a, XML_fgClr);
1724 WriteColor(::Color(ColorTransparency, rHatch.Color), nAlpha);
1725 mpFS->endElementNS( XML_a , XML_fgClr );
1726
1727 ::Color nColor = COL_WHITE;
1728
1729 if ( GetProperty( rXPropSet, "FillBackground" ) )
1730 {
1731 bool isBackgroundFilled = false;
1732 mAny >>= isBackgroundFilled;
1733 if( isBackgroundFilled )
1734 {
1735 if( GetProperty( rXPropSet, "FillColor" ) )
1736 {
1737 mAny >>= nColor;
1738 }
1739 }
1740 else
1741 nAlpha = 0;
1742 }
1743
1744 mpFS->startElementNS(XML_a, XML_bgClr);
1745 WriteColor(nColor, nAlpha);
1746 mpFS->endElementNS( XML_a , XML_bgClr );
1747
1748 mpFS->endElementNS( XML_a , XML_pattFill );
1749}
1750
1751void DrawingML::WriteGraphicCropProperties(uno::Reference<beans::XPropertySet> const & rXPropSet,
1752 Size const & rOriginalSize,
1753 MapMode const & rMapMode)
1754{
1755 if (!GetProperty(rXPropSet, "GraphicCrop"))
1756 return;
1757
1758 css::text::GraphicCrop aGraphicCropStruct;
1759 mAny >>= aGraphicCropStruct;
1760
1761 if(GetProperty(rXPropSet, "CustomShapeGeometry"))
1762 {
1763 // tdf#134210 GraphicCrop property is handled in import filter because of LibreOffice has not core
1764 // feature. We cropped the bitmap physically and MSO shouldn't crop bitmap one more time. When we
1765 // have core feature for graphic cropping in custom shapes, we should uncomment the code anymore.
1766
1767 mpFS->singleElementNS( XML_a, XML_srcRect);
1768 }
1769 else
1770 {
1771 Size aOriginalSize(rOriginalSize);
1772
1773 // GraphicCrop is in mm100, so in case the original size is in pixels, convert it over.
1774 if (rMapMode.GetMapUnit() == MapUnit::MapPixel)
1775 aOriginalSize = Application::GetDefaultDevice()->PixelToLogic(aOriginalSize, MapMode(MapUnit::Map100thMM));
1776
1777 if ( (0 != aGraphicCropStruct.Left) || (0 != aGraphicCropStruct.Top) || (0 != aGraphicCropStruct.Right) || (0 != aGraphicCropStruct.Bottom) )
1778 {
1779 mpFS->singleElementNS( XML_a, XML_srcRect,
1780 XML_l, OString::number(rtl::math::round(aGraphicCropStruct.Left * 100000.0 / aOriginalSize.Width())),
1781 XML_t, OString::number(rtl::math::round(aGraphicCropStruct.Top * 100000.0 / aOriginalSize.Height())),
1782 XML_r, OString::number(rtl::math::round(aGraphicCropStruct.Right * 100000.0 / aOriginalSize.Width())),
1783 XML_b, OString::number(rtl::math::round(aGraphicCropStruct.Bottom * 100000.0 / aOriginalSize.Height())) );
1784 }
1785 }
1786}
1787
1788void DrawingML::WriteSrcRectXGraphic(uno::Reference<beans::XPropertySet> const & rxPropertySet,
1789 uno::Reference<graphic::XGraphic> const & rxGraphic)
1790{
1791 Graphic aGraphic(rxGraphic);
1792 Size aOriginalSize = aGraphic.GetPrefSize();
1793 const MapMode& rMapMode = aGraphic.GetPrefMapMode();
1794 WriteGraphicCropProperties(rxPropertySet, aOriginalSize, rMapMode);
1795}
1796
1797void DrawingML::WriteXGraphicStretch(uno::Reference<beans::XPropertySet> const & rXPropSet,
1798 uno::Reference<graphic::XGraphic> const & rxGraphic)
1799{
1801 {
1802 // Limiting the area used for stretching is not supported in Impress.
1803 mpFS->singleElementNS(XML_a, XML_stretch);
1804 return;
1805 }
1806
1807 mpFS->startElementNS(XML_a, XML_stretch);
1808
1809 bool bCrop = false;
1810 if (GetProperty(rXPropSet, "GraphicCrop"))
1811 {
1812 css::text::GraphicCrop aGraphicCropStruct;
1813 mAny >>= aGraphicCropStruct;
1814
1815 if ((0 != aGraphicCropStruct.Left)
1816 || (0 != aGraphicCropStruct.Top)
1817 || (0 != aGraphicCropStruct.Right)
1818 || (0 != aGraphicCropStruct.Bottom))
1819 {
1820 Graphic aGraphic(rxGraphic);
1821 Size aOriginalSize(aGraphic.GetPrefSize());
1822 mpFS->singleElementNS(XML_a, XML_fillRect,
1823 XML_l, OString::number(((aGraphicCropStruct.Left) * 100000) / aOriginalSize.Width()),
1824 XML_t, OString::number(((aGraphicCropStruct.Top) * 100000) / aOriginalSize.Height()),
1825 XML_r, OString::number(((aGraphicCropStruct.Right) * 100000) / aOriginalSize.Width()),
1826 XML_b, OString::number(((aGraphicCropStruct.Bottom) * 100000) / aOriginalSize.Height()));
1827 bCrop = true;
1828 }
1829 }
1830
1831 if (!bCrop)
1832 {
1833 mpFS->singleElementNS(XML_a, XML_fillRect);
1834 }
1835
1836 mpFS->endElementNS(XML_a, XML_stretch);
1837}
1838
1839namespace
1840{
1841bool IsTopGroupObj(const uno::Reference<drawing::XShape>& xShape)
1842{
1844 if (!pObject)
1845 return false;
1846
1847 if (pObject->getParentSdrObjectFromSdrObject())
1848 return false;
1849
1850 return pObject->IsGroupObject();
1851}
1852}
1853
1855 sal_Int32 nXmlNamespace, bool bFlipH, bool bFlipV, sal_Int32 nRotation, bool bIsGroupShape)
1856{
1857
1858 mpFS->startElementNS( nXmlNamespace, XML_xfrm,
1859 XML_flipH, sax_fastparser::UseIf("1", bFlipH),
1860 XML_flipV, sax_fastparser::UseIf("1", bFlipV),
1861 XML_rot, sax_fastparser::UseIf(OString::number(nRotation), nRotation % 21600000 != 0));
1862
1863 sal_Int32 nLeft = rRect.Left();
1864 sal_Int32 nTop = rRect.Top();
1865 if (GetDocumentType() == DOCUMENT_DOCX && !m_xParent.is())
1866 {
1867 nLeft = 0;
1868 nTop = 0;
1869 }
1870 sal_Int32 nChildLeft = nLeft;
1871 sal_Int32 nChildTop = nTop;
1872
1873 mpFS->singleElementNS(XML_a, XML_off,
1874 XML_x, OString::number(oox::drawingml::convertHmmToEmu(nLeft)),
1875 XML_y, OString::number(oox::drawingml::convertHmmToEmu(nTop)));
1876 mpFS->singleElementNS(XML_a, XML_ext,
1877 XML_cx, OString::number(oox::drawingml::convertHmmToEmu(rRect.GetWidth())),
1878 XML_cy, OString::number(oox::drawingml::convertHmmToEmu(rRect.GetHeight())));
1879
1880 if (bIsGroupShape && (GetDocumentType() != DOCUMENT_DOCX || IsTopGroupObj(xShape)))
1881 {
1882 mpFS->singleElementNS(XML_a, XML_chOff,
1883 XML_x, OString::number(oox::drawingml::convertHmmToEmu(nChildLeft)),
1884 XML_y, OString::number(oox::drawingml::convertHmmToEmu(nChildTop)));
1885 mpFS->singleElementNS(XML_a, XML_chExt,
1886 XML_cx, OString::number(oox::drawingml::convertHmmToEmu(rRect.GetWidth())),
1887 XML_cy, OString::number(oox::drawingml::convertHmmToEmu(rRect.GetHeight())));
1888 }
1889
1890 mpFS->endElementNS( nXmlNamespace, XML_xfrm );
1891}
1892
1893void DrawingML::WriteShapeTransformation( const Reference< XShape >& rXShape, sal_Int32 nXmlNamespace, bool bFlipH, bool bFlipV, bool bSuppressRotation, bool bSuppressFlipping, bool bFlippedBeforeRotation )
1894{
1895 SAL_INFO("oox.shape", "write shape transformation");
1896
1897 Degree100 nRotation;
1898 Degree100 nCameraRotation;
1899 awt::Point aPos = rXShape->getPosition();
1900 awt::Size aSize = rXShape->getSize();
1901
1902 bool bFlipHWrite = bFlipH && !bSuppressFlipping;
1903 bool bFlipVWrite = bFlipV && !bSuppressFlipping;
1904 bFlipH = bFlipH && !bFlippedBeforeRotation;
1905 bFlipV = bFlipV && !bFlippedBeforeRotation;
1906
1907 if (GetDocumentType() == DOCUMENT_DOCX && m_xParent.is())
1908 {
1909 awt::Point aParentPos = m_xParent->getPosition();
1910 aPos.X -= aParentPos.X;
1911 aPos.Y -= aParentPos.Y;
1912 }
1913
1914 if ( aSize.Width < 0 )
1915 aSize.Width = 1000;
1916 if ( aSize.Height < 0 )
1917 aSize.Height = 1000;
1918 if (!bSuppressRotation)
1919 {
1921 nRotation = pShape ? pShape->GetRotateAngle() : 0_deg100;
1922 if ( GetDocumentType() != DOCUMENT_DOCX )
1923 {
1924 int faccos=bFlipV ? -1 : 1;
1925 int facsin=bFlipH ? -1 : 1;
1926 aPos.X-=(1-faccos*cos(toRadians(nRotation)))*aSize.Width/2-facsin*sin(toRadians(nRotation))*aSize.Height/2;
1927 aPos.Y-=(1-faccos*cos(toRadians(nRotation)))*aSize.Height/2+facsin*sin(toRadians(nRotation))*aSize.Width/2;
1928 }
1929 else if (m_xParent.is() && nRotation != 0_deg100)
1930 {
1931 // Position for rotated shapes inside group is not set by DocxSdrExport.
1932 basegfx::B2DRange aRect(-aSize.Width / 2.0, -aSize.Height / 2.0, aSize.Width / 2.0,
1933 aSize.Height / 2.0);
1934 basegfx::B2DHomMatrix aRotateMatrix =
1936 aRect.transform(aRotateMatrix);
1937 aPos.X += -aSize.Width / 2.0 - aRect.getMinX();
1938 aPos.Y += -aSize.Height / 2.0 - aRect.getMinY();
1939 }
1940
1941 // The RotateAngle property's value is independent from any flipping, and that's exactly what we need here.
1942 uno::Reference<beans::XPropertySet> xPropertySet(rXShape, uno::UNO_QUERY);
1943 uno::Reference<beans::XPropertySetInfo> xPropertySetInfo = xPropertySet->getPropertySetInfo();
1944 if (xPropertySetInfo->hasPropertyByName("RotateAngle"))
1945 {
1946 sal_Int32 nTmp;
1947 if (xPropertySet->getPropertyValue("RotateAngle") >>= nTmp)
1948 nRotation = Degree100(nTmp);
1949 }
1950 // tdf#133037: restore original rotate angle before output
1951 if (nRotation && xPropertySetInfo->hasPropertyByName(UNO_NAME_MISC_OBJ_INTEROPGRABBAG))
1952 {
1953 uno::Sequence<beans::PropertyValue> aGrabBagProps;
1954 xPropertySet->getPropertyValue(UNO_NAME_MISC_OBJ_INTEROPGRABBAG) >>= aGrabBagProps;
1955 auto p3DEffectProps = std::find_if(std::cbegin(aGrabBagProps), std::cend(aGrabBagProps),
1956 [](const PropertyValue& rProp) { return rProp.Name == "3DEffectProperties"; });
1957 if (p3DEffectProps != std::cend(aGrabBagProps))
1958 {
1959 uno::Sequence<beans::PropertyValue> a3DEffectProps;
1960 p3DEffectProps->Value >>= a3DEffectProps;
1961 auto pCameraProps = std::find_if(std::cbegin(a3DEffectProps), std::cend(a3DEffectProps),
1962 [](const PropertyValue& rProp) { return rProp.Name == "Camera"; });
1963 if (pCameraProps != std::cend(a3DEffectProps))
1964 {
1965 uno::Sequence<beans::PropertyValue> aCameraProps;
1966 pCameraProps->Value >>= aCameraProps;
1967 auto pZRotationProp = std::find_if(std::cbegin(aCameraProps), std::cend(aCameraProps),
1968 [](const PropertyValue& rProp) { return rProp.Name == "rotRev"; });
1969 if (pZRotationProp != std::cend(aCameraProps))
1970 {
1971 sal_Int32 nTmp = 0;
1972 pZRotationProp->Value >>= nTmp;
1973 nCameraRotation = NormAngle36000(Degree100(nTmp / -600));
1974 }
1975 }
1976 }
1977 }
1978 }
1979
1980 // OOXML flips shapes before rotating them.
1981 if(bFlipH != bFlipV)
1982 nRotation = 36000_deg100 - nRotation;
1983
1984 WriteTransformation(rXShape, tools::Rectangle(Point(aPos.X, aPos.Y), Size(aSize.Width, aSize.Height)), nXmlNamespace,
1985 bFlipHWrite, bFlipVWrite, ExportRotateClockwisify(nRotation + nCameraRotation), IsGroupShape( rXShape ));
1986}
1987
1988static OUString lcl_GetTarget(const css::uno::Reference<css::frame::XModel>& xModel, OUString& rURL)
1989{
1990 Reference<drawing::XDrawPagesSupplier> xDPS(xModel, uno::UNO_QUERY_THROW);
1991 Reference<drawing::XDrawPages> xDrawPages(xDPS->getDrawPages(), uno::UNO_SET_THROW);
1992 sal_uInt32 nPageCount = xDrawPages->getCount();
1993 OUString sTarget;
1994
1995 for (sal_uInt32 i = 0; i < nPageCount; ++i)
1996 {
1997 Reference<XDrawPage> xDrawPage;
1998 xDrawPages->getByIndex(i) >>= xDrawPage;
1999 Reference<container::XNamed> xNamed(xDrawPage, UNO_QUERY);
2000 if (!xNamed)
2001 continue;
2002 OUString sSlideName = "#" + xNamed->getName();
2003 if (rURL == sSlideName)
2004 {
2005 sTarget = "slide" + OUString::number(i + 1) + ".xml";
2006 break;
2007 }
2008 }
2009 if (sTarget.isEmpty())
2010 {
2011 sal_Int32 nSplit = rURL.lastIndexOf(' ');
2012 if (nSplit > -1)
2013 sTarget = OUString::Concat("slide") + rURL.subView(nSplit + 1) + ".xml";
2014 }
2015
2016 return sTarget;
2017}
2018
2019void DrawingML::WriteRunProperties( const Reference< XPropertySet >& rRun, bool bIsField, sal_Int32 nElement,
2020 bool bCheckDirect,bool& rbOverridingCharHeight, sal_Int32& rnCharHeight,
2021 sal_Int16 nScriptType, const Reference< XPropertySet >& rXShapePropSet)
2022{
2023 Reference< XPropertySet > rXPropSet = rRun;
2024 Reference< XPropertyState > rXPropState( rRun, UNO_QUERY );
2025 OUString usLanguage;
2026 PropertyState eState;
2027 bool bComplex = ( nScriptType == css::i18n::ScriptType::COMPLEX );
2028 const char* bold = "0";
2029 const char* italic = nullptr;
2030 const char* underline = nullptr;
2031 const char* strikeout = nullptr;
2032 const char* cap = nullptr;
2033 sal_Int32 nSize = 1800;
2034 sal_Int32 nCharEscapement = 0;
2035 sal_Int32 nCharKerning = 0;
2036 sal_Int32 nCharEscapementHeight = 0;
2037
2038 if ( nElement == XML_endParaRPr && rbOverridingCharHeight )
2039 {
2040 nSize = rnCharHeight;
2041 }
2042 else if (GetProperty(rXPropSet, "CharHeight"))
2043 {
2044 nSize = static_cast<sal_Int32>(100*(*o3tl::doAccess<float>(mAny)));
2045 if ( nElement == XML_rPr || nElement == XML_defRPr )
2046 {
2047 rbOverridingCharHeight = true;
2048 rnCharHeight = nSize;
2049 }
2050 }
2051
2052 if (GetProperty(rXPropSet, "CharKerning"))
2053 nCharKerning = static_cast<sal_Int32>(*o3tl::doAccess<sal_Int16>(mAny));
2060 nCharKerning = ((nCharKerning * 720)-360) / 254;
2061
2062 if ((bComplex && GetProperty(rXPropSet, "CharWeightComplex"))
2063 || GetProperty(rXPropSet, "CharWeight"))
2064 {
2065 if ( *o3tl::doAccess<float>(mAny) >= awt::FontWeight::SEMIBOLD )
2066 bold = "1";
2067 }
2068
2069 if ((bComplex && GetProperty(rXPropSet, "CharPostureComplex"))
2070 || GetProperty(rXPropSet, "CharPosture"))
2071 switch ( *o3tl::doAccess<awt::FontSlant>(mAny) )
2072 {
2073 case awt::FontSlant_OBLIQUE :
2074 case awt::FontSlant_ITALIC :
2075 italic = "1";
2076 break;
2077 default:
2078 break;
2079 }
2080
2081 if ((bCheckDirect && GetPropertyAndState(rXPropSet, rXPropState, "CharUnderline", eState)
2082 && eState == beans::PropertyState_DIRECT_VALUE)
2083 || GetProperty(rXPropSet, "CharUnderline"))
2084 {
2085 switch ( *o3tl::doAccess<sal_Int16>(mAny) )
2086 {
2087 case awt::FontUnderline::SINGLE :
2088 underline = "sng";
2089 break;
2090 case awt::FontUnderline::DOUBLE :
2091 underline = "dbl";
2092 break;
2093 case awt::FontUnderline::DOTTED :
2094 underline = "dotted";
2095 break;
2096 case awt::FontUnderline::DASH :
2097 underline = "dash";
2098 break;
2099 case awt::FontUnderline::LONGDASH :
2100 underline = "dashLong";
2101 break;
2102 case awt::FontUnderline::DASHDOT :
2103 underline = "dotDash";
2104 break;
2105 case awt::FontUnderline::DASHDOTDOT :
2106 underline = "dotDotDash";
2107 break;
2108 case awt::FontUnderline::WAVE :
2109 underline = "wavy";
2110 break;
2111 case awt::FontUnderline::DOUBLEWAVE :
2112 underline = "wavyDbl";
2113 break;
2114 case awt::FontUnderline::BOLD :
2115 underline = "heavy";
2116 break;
2117 case awt::FontUnderline::BOLDDOTTED :
2118 underline = "dottedHeavy";
2119 break;
2120 case awt::FontUnderline::BOLDDASH :
2121 underline = "dashHeavy";
2122 break;
2123 case awt::FontUnderline::BOLDLONGDASH :
2124 underline = "dashLongHeavy";
2125 break;
2126 case awt::FontUnderline::BOLDDASHDOT :
2127 underline = "dotDashHeavy";
2128 break;
2129 case awt::FontUnderline::BOLDDASHDOTDOT :
2130 underline = "dotDotDashHeavy";
2131 break;
2132 case awt::FontUnderline::BOLDWAVE :
2133 underline = "wavyHeavy";
2134 break;
2135 }
2136 }
2137
2138 if ((bCheckDirect && GetPropertyAndState(rXPropSet, rXPropState, "CharStrikeout", eState)
2139 && eState == beans::PropertyState_DIRECT_VALUE)
2140 || GetProperty(rXPropSet, "CharStrikeout"))
2141 {
2142 switch ( *o3tl::doAccess<sal_Int16>(mAny) )
2143 {
2144 case awt::FontStrikeout::NONE :
2145 strikeout = "noStrike";
2146 break;
2147 case awt::FontStrikeout::SINGLE :
2148 // LibO supports further values of character
2149 // strikeout, OOXML standard (20.1.10.78,
2150 // ST_TextStrikeType) however specifies only
2151 // 3 - single, double and none. Approximate
2152 // the remaining ones by single strike (better
2153 // some strike than none at all).
2154 // TODO: figure out how to do this better
2155 case awt::FontStrikeout::BOLD :
2156 case awt::FontStrikeout::SLASH :
2157 case awt::FontStrikeout::X :
2158 strikeout = "sngStrike";
2159 break;
2160 case awt::FontStrikeout::DOUBLE :
2161 strikeout = "dblStrike";
2162 break;
2163 }
2164 }
2165
2166 bool bLang = false;
2167 switch(nScriptType)
2168 {
2169 case css::i18n::ScriptType::ASIAN:
2170 bLang = GetProperty(rXPropSet, "CharLocaleAsian"); break;
2171 case css::i18n::ScriptType::COMPLEX:
2172 bLang = GetProperty(rXPropSet, "CharLocaleComplex"); break;
2173 default:
2174 bLang = GetProperty(rXPropSet, "CharLocale"); break;
2175 }
2176
2177 if (bLang)
2178 {
2179 css::lang::Locale aLocale;
2180 mAny >>= aLocale;
2181 LanguageTag aLanguageTag( aLocale);
2182 if (!aLanguageTag.isSystemLocale())
2183 usLanguage = aLanguageTag.getBcp47MS();
2184 }
2185
2186 if (GetPropertyAndState(rXPropSet, rXPropState, "CharEscapement", eState)
2187 && eState == beans::PropertyState_DIRECT_VALUE)
2188 mAny >>= nCharEscapement;
2189
2190 if (GetPropertyAndState(rXPropSet, rXPropState, "CharEscapementHeight", eState)
2191 && eState == beans::PropertyState_DIRECT_VALUE)
2192 mAny >>= nCharEscapementHeight;
2193
2194 if (DFLT_ESC_AUTO_SUPER == nCharEscapement)
2195 {
2196 // Raised by the differences between the ascenders (ascent = baseline to top of highest letter).
2197 // The ascent is generally about 80% of the total font height.
2198 // That is why DFLT_ESC_PROP (58) leads to 33% (DFLT_ESC_SUPER)
2199 nCharEscapement = .8 * (100 - nCharEscapementHeight);
2200 }
2201 else if (DFLT_ESC_AUTO_SUB == nCharEscapement)
2202 {
2203 // Lowered by the differences between the descenders (descent = baseline to bottom of lowest letter).
2204 // The descent is generally about 20% of the total font height.
2205 // That is why DFLT_ESC_PROP (58) leads to 8% (DFLT_ESC_SUB)
2206 nCharEscapement = .2 * -(100 - nCharEscapementHeight);
2207 }
2208
2209 if (nCharEscapement && nCharEscapementHeight)
2210 {
2211 nSize = (nSize * nCharEscapementHeight) / 100;
2212 // MSO uses default ~58% size
2213 nSize = (nSize / 0.58);
2214 }
2215
2216 if (GetProperty(rXPropSet, "CharCaseMap"))
2217 {
2218 switch ( *o3tl::doAccess<sal_Int16>(mAny) )
2219 {
2220 case CaseMap::UPPERCASE :
2221 cap = "all";
2222 break;
2223 case CaseMap::SMALLCAPS :
2224 cap = "small";
2225 break;
2226 }
2227 }
2228
2229 mpFS->startElementNS( XML_a, nElement,
2230 XML_b, bold,
2231 XML_i, italic,
2232 XML_lang, sax_fastparser::UseIf(usLanguage, !usLanguage.isEmpty()),
2233 XML_sz, OString::number(nSize),
2234 // For Condensed character spacing spc value is negative.
2235 XML_spc, sax_fastparser::UseIf(OString::number(nCharKerning), nCharKerning != 0),
2236 XML_strike, strikeout,
2237 XML_u, underline,
2238 XML_baseline, sax_fastparser::UseIf(OString::number(nCharEscapement*1000), nCharEscapement != 0),
2239 XML_cap, cap );
2240
2241 // Fontwork-shapes in LO have text outline and fill from shape stroke and shape fill
2242 // PowerPoint has this as run properties
2243 if (IsFontworkShape(rXShapePropSet))
2244 {
2245 WriteOutline(rXShapePropSet);
2246 WriteBlipOrNormalFill(rXShapePropSet, "Graphic");
2247 WriteShapeEffects(rXShapePropSet);
2248 }
2249 else
2250 {
2251 // mso doesn't like text color to be placed after typeface
2252 if ((bCheckDirect && GetPropertyAndState(rXPropSet, rXPropState, "CharColor", eState)
2253 && eState == beans::PropertyState_DIRECT_VALUE)
2254 || GetProperty(rXPropSet, "CharColor"))
2255 {
2256 ::Color color( ColorTransparency, *o3tl::doAccess<sal_uInt32>(mAny) );
2257 SAL_INFO("oox.shape", "run color: " << sal_uInt32(color) << " auto: " << sal_uInt32(COL_AUTO));
2258
2259 // WriteSolidFill() handles MAX_PERCENT as "no transparency".
2260 sal_Int32 nTransparency = MAX_PERCENT;
2261 if (rXPropSet->getPropertySetInfo()->hasPropertyByName("CharTransparence"))
2262 {
2263 rXPropSet->getPropertyValue("CharTransparence") >>= nTransparency;
2264 // UNO scale is 0..100, OOXML scale is 0..100000; also UNO tracks transparency, OOXML
2265 // tracks opacity.
2266 nTransparency = MAX_PERCENT - (nTransparency * PER_PERCENT);
2267 }
2268
2269 bool bContoured = false;
2270 if (GetProperty(rXPropSet, "CharContoured"))
2271 bContoured = *o3tl::doAccess<bool>(mAny);
2272
2273 // tdf#127696 If the CharContoured is true, then the text color is white and the outline color is the CharColor.
2274 if (bContoured)
2275 {
2276 mpFS->startElementNS(XML_a, XML_ln);
2277 if (color == COL_AUTO)
2278 {
2280 }
2281 else
2282 {
2283 color.SetAlpha(255);
2284 if (!WriteCharColor(rXPropSet))
2285 WriteSolidFill(color, nTransparency);
2286 }
2287 mpFS->endElementNS(XML_a, XML_ln);
2288
2290 }
2291 // tdf#104219 In LibreOffice and MS Office, there are two types of colors:
2292 // Automatic and Fixed. OOXML is setting automatic color, by not providing color.
2293 else if( color != COL_AUTO )
2294 {
2295 color.SetAlpha(255);
2296 // TODO: special handle embossed/engraved
2297 if (!WriteCharColor(rXPropSet))
2298 {
2299 WriteSolidFill(color, nTransparency);
2300 }
2301 }
2302 else if (GetDocumentType() == DOCUMENT_PPTX)
2303 {
2304 // Resolve COL_AUTO for PPTX since MS Powerpoint doesn't have automatic colors.
2305 bool bIsTextBackgroundDark = mbIsBackgroundDark;
2306 if (rXShapePropSet.is() && GetProperty(rXShapePropSet, "FillStyle")
2307 && mAny.get<FillStyle>() != FillStyle_NONE
2308 && GetProperty(rXShapePropSet, "FillColor"))
2309 {
2310 ::Color aShapeFillColor(ColorTransparency, mAny.get<sal_uInt32>());
2311 bIsTextBackgroundDark = aShapeFillColor.IsDark();
2312 }
2313
2314 if (bIsTextBackgroundDark)
2316 else
2318 }
2319 }
2320 }
2321
2322 // tdf#128096, exporting XML_highlight to docx already works fine,
2323 // so make sure this code is only run when exporting to pptx, just in case
2325 {
2326 if (GetProperty(rXPropSet, "CharBackColor"))
2327 {
2328 ::Color color(ColorTransparency, *o3tl::doAccess<sal_uInt32>(mAny));
2329 if( color != COL_AUTO )
2330 {
2331 mpFS->startElementNS(XML_a, XML_highlight);
2332 WriteColor( color );
2333 mpFS->endElementNS( XML_a, XML_highlight );
2334 }
2335 }
2336 }
2337
2338 if (underline
2339 && ((bCheckDirect
2340 && GetPropertyAndState(rXPropSet, rXPropState, "CharUnderlineColor", eState)
2341 && eState == beans::PropertyState_DIRECT_VALUE)
2342 || GetProperty(rXPropSet, "CharUnderlineColor")))
2343 {
2344 ::Color color(ColorTransparency, *o3tl::doAccess<sal_uInt32>(mAny));
2345 // if color is automatic, then we shouldn't write information about color but to take color from character
2346 if( color != COL_AUTO )
2347 {
2348 mpFS->startElementNS(XML_a, XML_uFill);
2350 mpFS->endElementNS( XML_a, XML_uFill );
2351 }
2352 else
2353 {
2354 mpFS->singleElementNS(XML_a, XML_uFillTx);
2355 }
2356 }
2357
2358 if (GetProperty(rXPropSet, "CharFontName"))
2359 {
2360 const char* const pitch = nullptr;
2361 const char* const charset = nullptr;
2362 OUString usTypeface;
2363
2364 mAny >>= usTypeface;
2365 OUString aSubstName( GetSubsFontName( usTypeface, SubsFontFlags::ONLYONE | SubsFontFlags::MS ) );
2366 if (!aSubstName.isEmpty())
2367 usTypeface = aSubstName;
2368
2369 mpFS->singleElementNS( XML_a, XML_latin,
2370 XML_typeface, usTypeface,
2371 XML_pitchFamily, pitch,
2372 XML_charset, charset );
2373 }
2374
2375 if ((bComplex
2376 && (GetPropertyAndState(rXPropSet, rXPropState, "CharFontNameComplex", eState)
2377 && eState == beans::PropertyState_DIRECT_VALUE))
2378 || (!bComplex
2379 && (GetPropertyAndState(rXPropSet, rXPropState, "CharFontNameAsian", eState)
2380 && eState == beans::PropertyState_DIRECT_VALUE)))
2381 {
2382 const char* const pitch = nullptr;
2383 const char* const charset = nullptr;
2384 OUString usTypeface;
2385
2386 mAny >>= usTypeface;
2387 OUString aSubstName( GetSubsFontName( usTypeface, SubsFontFlags::ONLYONE | SubsFontFlags::MS ) );
2388 if (!aSubstName.isEmpty())
2389 usTypeface = aSubstName;
2390
2391 mpFS->singleElementNS( XML_a, bComplex ? XML_cs : XML_ea,
2392 XML_typeface, usTypeface,
2393 XML_pitchFamily, pitch,
2394 XML_charset, charset );
2395 }
2396
2397 if( bIsField )
2398 {
2399 Reference< XTextField > rXTextField;
2400 if (GetProperty(rXPropSet, "TextField"))
2401 mAny >>= rXTextField;
2402 if( rXTextField.is() )
2403 rXPropSet.set( rXTextField, UNO_QUERY );
2404 }
2405
2406 // field properties starts here
2407 if (GetProperty(rXPropSet, "URL"))
2408 {
2409 OUString sURL;
2410
2411 mAny >>= sURL;
2412 if (!sURL.isEmpty())
2413 {
2414 if (!sURL.match("#action?jump="))
2415 {
2416 bool bExtURL = URLTransformer().isExternalURL(sURL);
2417 sURL = bExtURL ? sURL : lcl_GetTarget(GetFB()->getModel(), sURL);
2418
2419 OUString sRelId
2420 = mpFB->addRelation(mpFS->getOutputStream(),
2423 sURL, bExtURL);
2424
2425 if (bExtURL)
2426 mpFS->singleElementNS(XML_a, XML_hlinkClick, FSNS(XML_r, XML_id), sRelId);
2427 else
2428 mpFS->singleElementNS(XML_a, XML_hlinkClick, FSNS(XML_r, XML_id), sRelId,
2429 XML_action, "ppaction://hlinksldjump");
2430 }
2431 else
2432 {
2433 sal_Int32 nIndex = sURL.indexOf('=');
2434 std::u16string_view aDestination(sURL.subView(nIndex + 1));
2435 mpFS->singleElementNS(XML_a, XML_hlinkClick, FSNS(XML_r, XML_id), "", XML_action,
2436 OUString::Concat("ppaction://hlinkshowjump?jump=") + aDestination);
2437 }
2438 }
2439 }
2440 mpFS->endElementNS( XML_a, nElement );
2441}
2442
2443OUString DrawingML::GetFieldValue( const css::uno::Reference< css::text::XTextRange >& rRun, bool& bIsURLField )
2444{
2445 Reference< XPropertySet > rXPropSet( rRun, UNO_QUERY );
2446 OUString aFieldType, aFieldValue;
2447
2448 if (GetProperty(rXPropSet, "TextPortionType"))
2449 {
2450 aFieldType = *o3tl::doAccess<OUString>(mAny);
2451 SAL_INFO("oox.shape", "field type: " << aFieldType);
2452 }
2453
2454 if( aFieldType == "TextField" )
2455 {
2456 Reference< XTextField > rXTextField;
2457 if (GetProperty(rXPropSet, "TextField"))
2458 mAny >>= rXTextField;
2459 if( rXTextField.is() )
2460 {
2461 rXPropSet.set( rXTextField, UNO_QUERY );
2462 if( rXPropSet.is() )
2463 {
2464 OUString aFieldKind( rXTextField->getPresentation( true ) );
2465 SAL_INFO("oox.shape", "field kind: " << aFieldKind);
2466 if( aFieldKind == "Page" )
2467 {
2468 aFieldValue = "slidenum";
2469 }
2470 else if( aFieldKind == "Pages" )
2471 {
2472 aFieldValue = "slidecount";
2473 }
2474 else if( aFieldKind == "PageName" )
2475 {
2476 aFieldValue = "slidename";
2477 }
2478 else if( aFieldKind == "URL" )
2479 {
2480 bIsURLField = true;
2481 if (GetProperty(rXPropSet, "Representation"))
2482 mAny >>= aFieldValue;
2483
2484 }
2485 else if(aFieldKind == "Date")
2486 {
2487 sal_Int32 nNumFmt = -1;
2488 rXPropSet->getPropertyValue(UNO_TC_PROP_NUMFORMAT) >>= nNumFmt;
2489 aFieldValue = GetDatetimeTypeFromDate(static_cast<SvxDateFormat>(nNumFmt));
2490 }
2491 else if(aFieldKind == "ExtTime")
2492 {
2493 sal_Int32 nNumFmt = -1;
2494 rXPropSet->getPropertyValue(UNO_TC_PROP_NUMFORMAT) >>= nNumFmt;
2495 aFieldValue = GetDatetimeTypeFromTime(static_cast<SvxTimeFormat>(nNumFmt));
2496 }
2497 else if(aFieldKind == "ExtFile")
2498 {
2499 sal_Int32 nNumFmt = -1;
2500 rXPropSet->getPropertyValue(UNO_TC_PROP_FILE_FORMAT) >>= nNumFmt;
2501 switch(nNumFmt)
2502 {
2503 case 0: aFieldValue = "file"; // Path/File name
2504 break;
2505 case 1: aFieldValue = "file1"; // Path
2506 break;
2507 case 2: aFieldValue = "file2"; // File name without extension
2508 break;
2509 case 3: aFieldValue = "file3"; // File name with extension
2510 }
2511 }
2512 else if(aFieldKind == "Author")
2513 {
2514 aFieldValue = "author";
2515 }
2516 }
2517 }
2518 }
2519 return aFieldValue;
2520}
2521
2523{
2524 return GetDatetimeTypeFromDateTime(eDate, SvxTimeFormat::AppDefault);
2525}
2526
2528{
2529 return GetDatetimeTypeFromDateTime(SvxDateFormat::AppDefault, eTime);
2530}
2531
2533{
2534 OUString aDateField;
2535 switch (eDate)
2536 {
2537 case SvxDateFormat::StdSmall:
2538 case SvxDateFormat::A:
2539 aDateField = "datetime";
2540 break;
2541 case SvxDateFormat::B:
2542 aDateField = "datetime1"; // 13/02/1996
2543 break;
2544 case SvxDateFormat::C:
2545 aDateField = "datetime5";
2546 break;
2547 case SvxDateFormat::D:
2548 aDateField = "datetime3"; // 13 February 1996
2549 break;
2550 case SvxDateFormat::StdBig:
2551 case SvxDateFormat::E:
2552 case SvxDateFormat::F:
2553 aDateField = "datetime2";
2554 break;
2555 default:
2556 break;
2557 }
2558
2559 OUString aTimeField;
2560 switch (eTime)
2561 {
2562 case SvxTimeFormat::Standard:
2563 case SvxTimeFormat::HH24_MM_SS:
2564 case SvxTimeFormat::HH24_MM_SS_00:
2565 aTimeField = "datetime11"; // 13:49:38
2566 break;
2567 case SvxTimeFormat::HH24_MM:
2568 aTimeField = "datetime10"; // 13:49
2569 break;
2570 case SvxTimeFormat::HH12_MM:
2571 case SvxTimeFormat::HH12_MM_AMPM:
2572 aTimeField = "datetime12"; // 01:49 PM
2573 break;
2574 case SvxTimeFormat::HH12_MM_SS:
2575 case SvxTimeFormat::HH12_MM_SS_AMPM:
2576 case SvxTimeFormat::HH12_MM_SS_00:
2577 case SvxTimeFormat::HH12_MM_SS_00_AMPM:
2578 aTimeField = "datetime13"; // 01:49:38 PM
2579 break;
2580 default:
2581 break;
2582 }
2583
2584 if (!aDateField.isEmpty() && aTimeField.isEmpty())
2585 return aDateField;
2586 else if (!aTimeField.isEmpty() && aDateField.isEmpty())
2587 return aTimeField;
2588 else if (!aDateField.isEmpty() && !aTimeField.isEmpty())
2589 {
2590 if (aTimeField == "datetime11" || aTimeField == "datetime13")
2591 // only datetime format that has Date and HH:MM:SS
2592 return "datetime9"; // dd/mm/yyyy H:MM:SS
2593 else
2594 // only datetime format that has Date and HH:MM
2595 return "datetime8"; // dd/mm/yyyy H:MM
2596 }
2597 else
2598 return "";
2599}
2600
2602 bool& rbOverridingCharHeight, sal_Int32& rnCharHeight,
2603 const css::uno::Reference< css::beans::XPropertySet >& rXShapePropSet)
2604{
2605 Reference< XPropertySet > rXPropSet( rRun, UNO_QUERY );
2606 sal_Int16 nLevel = -1;
2607 if (GetProperty(rXPropSet, "NumberingLevel"))
2608 mAny >>= nLevel;
2609
2610 bool bNumberingIsNumber = true;
2611 if (GetProperty(rXPropSet, "NumberingIsNumber"))
2612 mAny >>= bNumberingIsNumber;
2613
2614 float nFontSize = -1;
2615 if (GetProperty(rXPropSet, "CharHeight"))
2616 mAny >>= nFontSize;
2617
2618 bool bIsURLField = false;
2619 OUString sFieldValue = GetFieldValue( rRun, bIsURLField );
2620 bool bWriteField = !( sFieldValue.isEmpty() || bIsURLField );
2621
2622 OUString sText = rRun->getString();
2623
2624 //if there is no text following the bullet, add a space after the bullet
2625 if (nLevel !=-1 && bNumberingIsNumber && sText.isEmpty() )
2626 sText=" ";
2627
2628 if ( bIsURLField )
2629 sText = sFieldValue;
2630
2631 if( sText.isEmpty())
2632 {
2633 Reference< XPropertySet > xPropSet( rRun, UNO_QUERY );
2634
2635 try
2636 {
2637 if( !xPropSet.is() || !( xPropSet->getPropertyValue( "PlaceholderText" ) >>= sText ) )
2638 return;
2639 if( sText.isEmpty() )
2640 return;
2641 }
2642 catch (const Exception &)
2643 {
2644 return;
2645 }
2646 }
2647
2648 if (sText == "\n")
2649 {
2650 // Empty run? Do not forget to write the font size in case of pptx:
2651 if ((GetDocumentType() == DOCUMENT_PPTX) && (nFontSize != -1))
2652 {
2653 mpFS->startElementNS(XML_a, XML_br);
2654 mpFS->singleElementNS(XML_a, XML_rPr, XML_sz,
2655 OString::number(nFontSize * 100).getStr());
2656 mpFS->endElementNS(XML_a, XML_br);
2657 }
2658 else
2659 mpFS->singleElementNS(XML_a, XML_br);
2660 }
2661 else
2662 {
2663 if( bWriteField )
2664 {
2665 OString sUUID(comphelper::xml::generateGUIDString());
2666 mpFS->startElementNS( XML_a, XML_fld,
2667 XML_id, sUUID.getStr(),
2668 XML_type, sFieldValue );
2669 }
2670 else
2671 {
2672 mpFS->startElementNS(XML_a, XML_r);
2673 }
2674
2675 Reference< XPropertySet > xPropSet( rRun, uno::UNO_QUERY );
2676
2677 WriteRunProperties( xPropSet, bIsURLField, XML_rPr, true, rbOverridingCharHeight, rnCharHeight, GetScriptType(sText), rXShapePropSet);
2678 mpFS->startElementNS(XML_a, XML_t);
2679 mpFS->writeEscaped( sText );
2680 mpFS->endElementNS( XML_a, XML_t );
2681
2682 if( bWriteField )
2683 mpFS->endElementNS( XML_a, XML_fld );
2684 else
2685 mpFS->endElementNS( XML_a, XML_r );
2686 }
2687}
2688
2689static OUString GetAutoNumType(SvxNumType nNumberingType, bool bSDot, bool bPBehind, bool bPBoth)
2690{
2691 OUString sPrefixSuffix;
2692
2693 if (bPBoth)
2694 sPrefixSuffix = "ParenBoth";
2695 else if (bPBehind)
2696 sPrefixSuffix = "ParenR";
2697 else if (bSDot)
2698 sPrefixSuffix = "Period";
2699
2700 switch( nNumberingType )
2701 {
2704 return "alphaUc" + sPrefixSuffix;
2705
2708 return "alphaLc" + sPrefixSuffix;
2709
2710 case SVX_NUM_ROMAN_UPPER :
2711 return "romanUc" + sPrefixSuffix;
2712
2713 case SVX_NUM_ROMAN_LOWER :
2714 return "romanLc" + sPrefixSuffix;
2715
2716 case SVX_NUM_ARABIC :
2717 {
2718 if (sPrefixSuffix.isEmpty())
2719 return "arabicPlain";
2720 else
2721 return "arabic" + sPrefixSuffix;
2722 }
2723 default:
2724 break;
2725 }
2726
2727 return OUString();
2728}
2729
2730void DrawingML::WriteParagraphNumbering(const Reference< XPropertySet >& rXPropSet, float fFirstCharHeight, sal_Int16 nLevel )
2731{
2732 if (nLevel < 0 || !GetProperty(rXPropSet, "NumberingRules"))
2733 return;
2734
2735 Reference< XIndexAccess > rXIndexAccess;
2736
2737 if (!(mAny >>= rXIndexAccess) || nLevel >= rXIndexAccess->getCount())
2738 return;
2739
2740 SAL_INFO("oox.shape", "numbering rules");
2741
2742 Sequence<PropertyValue> aPropertySequence;
2743 rXIndexAccess->getByIndex(nLevel) >>= aPropertySequence;
2744
2745 if (!aPropertySequence.hasElements())
2746 return;
2747
2748 SvxNumType nNumberingType = SVX_NUM_NUMBER_NONE;
2749 bool bSDot = false;
2750 bool bPBehind = false;
2751 bool bPBoth = false;
2752 sal_Unicode aBulletChar = 0x2022; // a bullet
2753 awt::FontDescriptor aFontDesc;
2754 bool bHasFontDesc = false;
2755 uno::Reference<graphic::XGraphic> xGraphic;
2756 sal_Int16 nBulletRelSize = 0;
2757 sal_Int16 nStartWith = 1;
2758 ::Color nBulletColor;
2759 bool bHasBulletColor = false;
2760 awt::Size aGraphicSize;
2761
2762 for ( const PropertyValue& rPropValue : std::as_const(aPropertySequence) )
2763 {
2764 OUString aPropName( rPropValue.Name );
2765 SAL_INFO("oox.shape", "pro name: " << aPropName);
2766 if ( aPropName == "NumberingType" )
2767 {
2768 nNumberingType = static_cast<SvxNumType>(*o3tl::doAccess<sal_Int16>(rPropValue.Value));
2769 }
2770 else if ( aPropName == "Prefix" )
2771 {
2772 if( *o3tl::doAccess<OUString>(rPropValue.Value) == ")")
2773 bPBoth = true;
2774 }
2775 else if ( aPropName == "Suffix" )
2776 {
2777 auto s = o3tl::doAccess<OUString>(rPropValue.Value);
2778 if( *s == ".")
2779 bSDot = true;
2780 else if( *s == ")")
2781 bPBehind = true;
2782 }
2783 else if(aPropName == "BulletColor")
2784 {
2785 nBulletColor = ::Color(ColorTransparency, *o3tl::doAccess<sal_uInt32>(rPropValue.Value));
2786 bHasBulletColor = true;
2787 }
2788 else if ( aPropName == "BulletChar" )
2789 {
2790 aBulletChar = (*o3tl::doAccess<OUString>(rPropValue.Value))[ 0 ];
2791 }
2792 else if ( aPropName == "BulletFont" )
2793 {
2794 aFontDesc = *o3tl::doAccess<awt::FontDescriptor>(rPropValue.Value);
2795 bHasFontDesc = true;
2796
2797 // Our numbullet dialog has set the wrong textencoding for our "StarSymbol" font,
2798 // instead of a Unicode encoding the encoding RTL_TEXTENCODING_SYMBOL was used.
2799 // Because there might exist a lot of damaged documents I added this two lines
2800 // which fixes the bullet problem for the export.
2801 if ( aFontDesc.Name.equalsIgnoreAsciiCase("StarSymbol") )
2802 aFontDesc.CharSet = RTL_TEXTENCODING_MS_1252;
2803
2804 }
2805 else if ( aPropName == "BulletRelSize" )
2806 {
2807 nBulletRelSize = *o3tl::doAccess<sal_Int16>(rPropValue.Value);
2808 }
2809 else if ( aPropName == "StartWith" )
2810 {
2811 nStartWith = *o3tl::doAccess<sal_Int16>(rPropValue.Value);
2812 }
2813 else if (aPropName == "GraphicBitmap")
2814 {
2815 auto xBitmap = rPropValue.Value.get<uno::Reference<awt::XBitmap>>();
2816 xGraphic.set(xBitmap, uno::UNO_QUERY);
2817 }
2818 else if ( aPropName == "GraphicSize" )
2819 {
2820 aGraphicSize = *o3tl::doAccess<awt::Size>(rPropValue.Value);
2821 SAL_INFO("oox.shape", "graphic size: " << aGraphicSize.Width << "x" << aGraphicSize.Height);
2822 }
2823 }
2824
2825 if (nNumberingType == SVX_NUM_NUMBER_NONE)
2826 return;
2827
2828 Graphic aGraphic(xGraphic);
2829 if (xGraphic.is() && aGraphic.GetType() != GraphicType::NONE)
2830 {
2831 tools::Long nFirstCharHeightMm = TransformMetric(fFirstCharHeight * 100.f, FieldUnit::POINT, FieldUnit::MM);
2832 float fBulletSizeRel = aGraphicSize.Height / static_cast<float>(nFirstCharHeightMm) / OOX_BULLET_LIST_SCALE_FACTOR;
2833
2834 OUString sRelationId;
2835
2836 if (fBulletSizeRel < 1.0f)
2837 {
2838 // Add padding to get the bullet point centered in PPT
2839 Size aDestSize(64, 64);
2840 float fBulletSizeRelX = fBulletSizeRel / aGraphicSize.Height * aGraphicSize.Width;
2841 tools::Long nPaddingX = std::max<tools::Long>(0, std::lround((aDestSize.Width() - fBulletSizeRelX * aDestSize.Width()) / 2.f));
2842 tools::Long nPaddingY = std::lround((aDestSize.Height() - fBulletSizeRel * aDestSize.Height()) / 2.f);
2843 tools::Rectangle aDestRect(nPaddingX, nPaddingY, aDestSize.Width() - nPaddingX, aDestSize.Height() - nPaddingY);
2844
2845 AlphaMask aMask(aDestSize);
2846 aMask.Erase(255);
2847 BitmapEx aSourceBitmap(aGraphic.GetBitmapEx());
2848 aSourceBitmap.Scale(aDestRect.GetSize());
2849 tools::Rectangle aSourceRect(Point(0, 0), aDestRect.GetSize());
2850 BitmapEx aDestBitmap(Bitmap(aDestSize, vcl::PixelFormat::N24_BPP), aMask);
2851 aDestBitmap.CopyPixel(aDestRect, aSourceRect, &aSourceBitmap);
2852 Graphic aDestGraphic(aDestBitmap);
2853 sRelationId = WriteImage(aDestGraphic);
2854 fBulletSizeRel = 1.0f;
2855 }
2856 else
2857 {
2858 sRelationId = WriteImage(aGraphic);
2859 }
2860
2861 mpFS->singleElementNS( XML_a, XML_buSzPct,
2862 XML_val, OString::number(std::min<sal_Int32>(std::lround(100000.f * fBulletSizeRel), 400000)));
2863 mpFS->startElementNS(XML_a, XML_buBlip);
2864 mpFS->singleElementNS(XML_a, XML_blip, FSNS(XML_r, XML_embed), sRelationId);
2865 mpFS->endElementNS( XML_a, XML_buBlip );
2866 }
2867 else
2868 {
2869 if(bHasBulletColor)
2870 {
2871 if (nBulletColor == COL_AUTO )
2872 {
2873 nBulletColor = ::Color(ColorTransparency, mbIsBackgroundDark ? 0xffffff : 0x000000);
2874 }
2875 mpFS->startElementNS(XML_a, XML_buClr);
2876 WriteColor( nBulletColor );
2877 mpFS->endElementNS( XML_a, XML_buClr );
2878 }
2879
2880 if( nBulletRelSize && nBulletRelSize != 100 )
2881 mpFS->singleElementNS( XML_a, XML_buSzPct,
2882 XML_val, OString::number(std::clamp<sal_Int32>(1000*nBulletRelSize, 25000, 400000)));
2883 if( bHasFontDesc )
2884 {
2885 if ( SVX_NUM_CHAR_SPECIAL == nNumberingType )
2886 aBulletChar = SubstituteBullet( aBulletChar, aFontDesc );
2887 mpFS->singleElementNS( XML_a, XML_buFont,
2888 XML_typeface, aFontDesc.Name,
2889 XML_charset, sax_fastparser::UseIf("2", aFontDesc.CharSet == awt::CharSet::SYMBOL));
2890 }
2891
2892 OUString aAutoNumType = GetAutoNumType( nNumberingType, bSDot, bPBehind, bPBoth );
2893
2894 if (!aAutoNumType.isEmpty())
2895 {
2896 mpFS->singleElementNS(XML_a, XML_buAutoNum,
2897 XML_type, aAutoNumType,
2898 XML_startAt, sax_fastparser::UseIf(OString::number(nStartWith), nStartWith > 1));
2899 }
2900 else
2901 {
2902 mpFS->singleElementNS(XML_a, XML_buChar, XML_char, OUString(aBulletChar));
2903 }
2904 }
2905}
2906
2908{
2909 css::uno::Sequence<css::style::TabStop> aTabStops;
2910 if (GetProperty(rXPropSet, "ParaTabStops"))
2911 aTabStops = *o3tl::doAccess<css::uno::Sequence<css::style::TabStop>>(mAny);
2912
2913 if (aTabStops.getLength() > 0)
2914 mpFS->startElementNS(XML_a, XML_tabLst);
2915
2916 for (const css::style::TabStop& rTabStop : std::as_const(aTabStops))
2917 {
2918 OString sPosition = OString::number(GetPointFromCoordinate(rTabStop.Position));
2919 OString sAlignment;
2920 switch (rTabStop.Alignment)
2921 {
2922 case css::style::TabAlign_DECIMAL:
2923 sAlignment = "dec";
2924 break;
2925 case css::style::TabAlign_RIGHT:
2926 sAlignment = "r";
2927 break;
2928 case css::style::TabAlign_CENTER:
2929 sAlignment = "ctr";
2930 break;
2931 case css::style::TabAlign_LEFT:
2932 default:
2933 sAlignment = "l";
2934 }
2935 mpFS->singleElementNS(XML_a, XML_tab, XML_algn, sAlignment, XML_pos, sPosition);
2936 }
2937 if (aTabStops.getLength() > 0)
2938 mpFS->endElementNS(XML_a, XML_tabLst);
2939}
2940
2942{
2943 bool bRet = false;
2944 if ( rXShape.is() )
2945 {
2946 uno::Reference<lang::XServiceInfo> xServiceInfo(rXShape, uno::UNO_QUERY_THROW);
2947 bRet = xServiceInfo->supportsService("com.sun.star.drawing.GroupShape");
2948 }
2949 return bRet;
2950}
2951
2952sal_Int32 DrawingML::getBulletMarginIndentation (const Reference< XPropertySet >& rXPropSet,sal_Int16 nLevel, std::u16string_view propName)
2953{
2954 if (nLevel < 0 || !GetProperty(rXPropSet, "NumberingRules"))
2955 return 0;
2956
2957 Reference< XIndexAccess > rXIndexAccess;
2958
2959 if (!(mAny >>= rXIndexAccess) || nLevel >= rXIndexAccess->getCount())
2960 return 0;
2961
2962 SAL_INFO("oox.shape", "numbering rules");
2963
2964 Sequence<PropertyValue> aPropertySequence;
2965 rXIndexAccess->getByIndex(nLevel) >>= aPropertySequence;
2966
2967 if (!aPropertySequence.hasElements())
2968 return 0;
2969
2970 for ( const PropertyValue& rPropValue : std::as_const(aPropertySequence) )
2971 {
2972 OUString aPropName( rPropValue.Name );
2973 SAL_INFO("oox.shape", "pro name: " << aPropName);
2974 if ( aPropName == propName )
2975 return *o3tl::doAccess<sal_Int32>(rPropValue.Value);
2976 }
2977
2978 return 0;
2979}
2980
2981const char* DrawingML::GetAlignment( style::ParagraphAdjust nAlignment )
2982{
2983 const char* sAlignment = nullptr;
2984
2985 switch( nAlignment )
2986 {
2987 case style::ParagraphAdjust_CENTER:
2988 sAlignment = "ctr";
2989 break;
2990 case style::ParagraphAdjust_RIGHT:
2991 sAlignment = "r";
2992 break;
2993 case style::ParagraphAdjust_BLOCK:
2994 sAlignment = "just";
2995 break;
2996 default:
2997 ;
2998 }
2999
3000 return sAlignment;
3001}
3002
3003void DrawingML::WriteLinespacing(const LineSpacing& rSpacing, float fFirstCharHeight)
3004{
3005 if( rSpacing.Mode == LineSpacingMode::PROP )
3006 {
3007 mpFS->singleElementNS( XML_a, XML_spcPct,
3008 XML_val, OString::number(static_cast<sal_Int32>(rSpacing.Height)*1000));
3009 }
3010 else if (rSpacing.Mode == LineSpacingMode::MINIMUM
3011 && fFirstCharHeight > static_cast<float>(rSpacing.Height) * 0.001 * 72.0 / 2.54)
3012 {
3013 // 100% proportional line spacing = single line spacing
3014 mpFS->singleElementNS(XML_a, XML_spcPct, XML_val,
3015 OString::number(static_cast<sal_Int32>(100000)));
3016 }
3017 else
3018 {
3019 mpFS->singleElementNS( XML_a, XML_spcPts,
3020 XML_val, OString::number(std::lround(rSpacing.Height / 25.4 * 72)));
3021 }
3022}
3023
3024bool DrawingML::WriteParagraphProperties( const Reference< XTextContent >& rParagraph, float fFirstCharHeight, sal_Int32 nElement)
3025{
3026 Reference< XPropertySet > rXPropSet( rParagraph, UNO_QUERY );
3027 Reference< XPropertyState > rXPropState( rParagraph, UNO_QUERY );
3028 PropertyState eState;
3029
3030 if( !rXPropSet.is() || !rXPropState.is() )
3031 return false;
3032
3033 sal_Int16 nLevel = -1;
3034 if (GetProperty(rXPropSet, "NumberingLevel"))
3035 mAny >>= nLevel;
3036
3037 bool bWriteNumbering = true;
3038 bool bForceZeroIndent = false;
3039 if (mbPlaceholder)
3040 {
3041 Reference< text::XTextRange > xParaText(rParagraph, UNO_QUERY);
3042 if (xParaText)
3043 {
3044 bool bNumberingOnThisLevel = false;
3045 if (nLevel > -1)
3046 {
3047 Reference< XIndexAccess > xNumberingRules(rXPropSet->getPropertyValue("NumberingRules"), UNO_QUERY);
3048 const PropertyValues& rNumRuleOfLevel = xNumberingRules->getByIndex(nLevel).get<PropertyValues>();
3049 for (const PropertyValue& rRule : rNumRuleOfLevel)
3050 if (rRule.Name == "NumberingType" && rRule.Value.hasValue())
3051 bNumberingOnThisLevel = rRule.Value.get<sal_uInt16>() != style::NumberingType::NUMBER_NONE;
3052 }
3053
3054 const bool bIsNumberingVisible = rXPropSet->getPropertyValue("NumberingIsNumber").get<bool>();
3055 const bool bIsLineEmpty = !xParaText->getString().getLength();
3056
3057 bWriteNumbering = !bIsLineEmpty && bIsNumberingVisible && (nLevel != -1);
3058 bForceZeroIndent = (!bIsNumberingVisible || bIsLineEmpty || !bNumberingOnThisLevel);
3059 }
3060
3061 }
3062
3063 sal_Int16 nTmp = sal_Int16(style::ParagraphAdjust_LEFT);
3064 if (GetProperty(rXPropSet, "ParaAdjust"))
3065 mAny >>= nTmp;
3066 style::ParagraphAdjust nAlignment = static_cast<style::ParagraphAdjust>(nTmp);
3067
3068 bool bHasLinespacing = false;
3069 LineSpacing aLineSpacing;
3070 if (GetPropertyAndState(rXPropSet, rXPropState, "ParaLineSpacing", eState)
3071 && (mAny >>= aLineSpacing)
3072 && (eState == beans::PropertyState_DIRECT_VALUE ||
3073 // only export if it differs from the default 100% line spacing
3074 aLineSpacing.Mode != LineSpacingMode::PROP || aLineSpacing.Height != 100))
3075 bHasLinespacing = true;
3076
3077 bool bRtl = false;
3078 if (GetProperty(rXPropSet, "WritingMode"))
3079 {
3080 sal_Int16 nWritingMode;
3081 if( ( mAny >>= nWritingMode ) && nWritingMode == text::WritingMode2::RL_TB )
3082 {
3083 bRtl = true;
3084 }
3085 }
3086
3087 sal_Int32 nParaLeftMargin = 0;
3088 sal_Int32 nParaFirstLineIndent = 0;
3089
3090 if (GetProperty(rXPropSet, "ParaLeftMargin"))
3091 mAny >>= nParaLeftMargin;
3092 if (GetProperty(rXPropSet, "ParaFirstLineIndent"))
3093 mAny >>= nParaFirstLineIndent;
3094
3095 sal_Int32 nParaTopMargin = 0;
3096 sal_Int32 nParaBottomMargin = 0;
3097
3098 if (GetProperty(rXPropSet, "ParaTopMargin"))
3099 mAny >>= nParaTopMargin;
3100 if (GetProperty(rXPropSet, "ParaBottomMargin"))
3101 mAny >>= nParaBottomMargin;
3102
3103 sal_Int32 nLeftMargin = getBulletMarginIndentation ( rXPropSet, nLevel,u"LeftMargin");
3104 sal_Int32 nLineIndentation = getBulletMarginIndentation ( rXPropSet, nLevel,u"FirstLineOffset");
3105
3106 if (bWriteNumbering && !bForceZeroIndent)
3107 {
3108 if (!(nLevel != -1
3109 || nAlignment != style::ParagraphAdjust_LEFT
3110 || bHasLinespacing))
3111 return false;
3112 }
3113
3114 if (nParaLeftMargin) // For Paragraph
3115 mpFS->startElementNS( XML_a, nElement,
3116 XML_lvl, sax_fastparser::UseIf(OString::number(nLevel), nLevel > 0),
3117 XML_marL, sax_fastparser::UseIf(OString::number(oox::drawingml::convertHmmToEmu(nParaLeftMargin)), nParaLeftMargin > 0),
3118 XML_indent, sax_fastparser::UseIf(OString::number(!bForceZeroIndent ? oox::drawingml::convertHmmToEmu(nParaFirstLineIndent) : 0), (bForceZeroIndent || (nParaFirstLineIndent != 0))),
3119 XML_algn, GetAlignment( nAlignment ),
3120 XML_rtl, sax_fastparser::UseIf(ToPsz10(bRtl), bRtl));
3121 else
3122 mpFS->startElementNS( XML_a, nElement,
3123 XML_lvl, sax_fastparser::UseIf(OString::number(nLevel), nLevel > 0),
3125 XML_indent, sax_fastparser::UseIf(OString::number(!bForceZeroIndent ? oox::drawingml::convertHmmToEmu(nLineIndentation) : 0), (bForceZeroIndent || ( nLineIndentation != 0))),
3126 XML_algn, GetAlignment( nAlignment ),
3127 XML_rtl, sax_fastparser::UseIf(ToPsz10(bRtl), bRtl));
3128
3129
3130 if( bHasLinespacing )
3131 {
3132 mpFS->startElementNS(XML_a, XML_lnSpc);
3133 WriteLinespacing(aLineSpacing, fFirstCharHeight);
3134 mpFS->endElementNS( XML_a, XML_lnSpc );
3135 }
3136
3137 if( nParaTopMargin != 0 )
3138 {
3139 mpFS->startElementNS(XML_a, XML_spcBef);
3140 {
3141 mpFS->singleElementNS( XML_a, XML_spcPts,
3142 XML_val, OString::number(std::lround(nParaTopMargin / 25.4 * 72)));
3143 }
3144 mpFS->endElementNS( XML_a, XML_spcBef );
3145 }
3146
3147 if( nParaBottomMargin != 0 )
3148 {
3149 mpFS->startElementNS(XML_a, XML_spcAft);
3150 {
3151 mpFS->singleElementNS( XML_a, XML_spcPts,
3152 XML_val, OString::number(std::lround(nParaBottomMargin / 25.4 * 72)));
3153 }
3154 mpFS->endElementNS( XML_a, XML_spcAft );
3155 }
3156
3157 if (!bWriteNumbering)
3158 mpFS->singleElementNS(XML_a, XML_buNone);
3159 else
3160 WriteParagraphNumbering( rXPropSet, fFirstCharHeight, nLevel );
3161
3162 WriteParagraphTabStops( rXPropSet );
3163
3164 // do not end element for lstStyles since, defRPr should be stacked inside it
3165 if( nElement != XML_lvl1pPr )
3166 mpFS->endElementNS( XML_a, nElement );
3167
3168 return true;
3169}
3170
3171void DrawingML::WriteLstStyles(const css::uno::Reference<css::text::XTextContent>& rParagraph,
3172 bool& rbOverridingCharHeight, sal_Int32& rnCharHeight,
3173 const css::uno::Reference<css::beans::XPropertySet>& rXShapePropSet)
3174{
3175 Reference<XEnumerationAccess> xAccess(rParagraph, UNO_QUERY);
3176 if (!xAccess.is())
3177 return;
3178
3179 Reference<XEnumeration> xEnumeration(xAccess->createEnumeration());
3180 if (!xEnumeration.is())
3181 return;
3182
3183
3185
3186 if (!xEnumeration->hasMoreElements())
3187 return;
3188
3189 Any aAny(xEnumeration->nextElement());
3190 if (aAny >>= rRun)
3191 {
3192 float fFirstCharHeight = rnCharHeight / 1000.;
3193 Reference<XPropertySet> xFirstRunPropSet(rRun, UNO_QUERY);
3194 Reference<XPropertySetInfo> xFirstRunPropSetInfo
3195 = xFirstRunPropSet->getPropertySetInfo();
3196
3197 if (xFirstRunPropSetInfo->hasPropertyByName("CharHeight"))
3198 fFirstCharHeight = xFirstRunPropSet->getPropertyValue("CharHeight").get<float>();
3199
3200 mpFS->startElementNS(XML_a, XML_lstStyle);
3201 if( !WriteParagraphProperties(rParagraph, fFirstCharHeight, XML_lvl1pPr) )
3202 mpFS->startElementNS(XML_a, XML_lvl1pPr);
3203 WriteRunProperties(xFirstRunPropSet, false, XML_defRPr, true, rbOverridingCharHeight,
3204 rnCharHeight, GetScriptType(rRun->getString()), rXShapePropSet);
3205 mpFS->endElementNS(XML_a, XML_lvl1pPr);
3206 mpFS->endElementNS(XML_a, XML_lstStyle);
3207 }
3208}
3209
3211 bool& rbOverridingCharHeight, sal_Int32& rnCharHeight,
3212 const css::uno::Reference< css::beans::XPropertySet >& rXShapePropSet)
3213{
3214 Reference< XEnumerationAccess > access( rParagraph, UNO_QUERY );
3215 if( !access.is() )
3216 return;
3217
3218 Reference< XEnumeration > enumeration( access->createEnumeration() );
3219 if( !enumeration.is() )
3220 return;
3221
3222 mpFS->startElementNS(XML_a, XML_p);
3223
3224 bool bPropertiesWritten = false;
3225 while( enumeration->hasMoreElements() )
3226 {
3228 Any any ( enumeration->nextElement() );
3229
3230 if (any >>= run)
3231 {
3232 if( !bPropertiesWritten )
3233 {
3234 float fFirstCharHeight = rnCharHeight / 1000.;
3235 Reference< XPropertySet > xFirstRunPropSet (run, UNO_QUERY);
3236 Reference< XPropertySetInfo > xFirstRunPropSetInfo = xFirstRunPropSet->getPropertySetInfo();
3237 if( xFirstRunPropSetInfo->hasPropertyByName("CharHeight") )
3238 {
3239 fFirstCharHeight = xFirstRunPropSet->getPropertyValue("CharHeight").get<float>();
3240 rnCharHeight = 100 * fFirstCharHeight;
3241 rbOverridingCharHeight = true;
3242 }
3243 WriteParagraphProperties(rParagraph, fFirstCharHeight, XML_pPr);
3244 bPropertiesWritten = true;
3245 }
3246 WriteRun( run, rbOverridingCharHeight, rnCharHeight, rXShapePropSet);
3247 }
3248 }
3249 Reference< XPropertySet > rXPropSet( rParagraph, UNO_QUERY );
3250 sal_Int16 nDummy = -1;
3251 WriteRunProperties(rXPropSet, false, XML_endParaRPr, false, rbOverridingCharHeight,
3252 rnCharHeight, nDummy, rXShapePropSet);
3253
3254 mpFS->endElementNS( XML_a, XML_p );
3255}
3256
3257bool DrawingML::IsFontworkShape(const css::uno::Reference<css::beans::XPropertySet>& rXShapePropSet)
3258{
3259 bool bResult(false);
3260 if (rXShapePropSet.is())
3261 {
3262 Sequence<PropertyValue> aCustomShapeGeometryProps;
3263 if (GetProperty(rXShapePropSet, "CustomShapeGeometry"))
3264 {
3265 mAny >>= aCustomShapeGeometryProps;
3266 uno::Sequence<beans::PropertyValue> aTextPathSeq;
3267 for (const auto& rProp : std::as_const(aCustomShapeGeometryProps))
3268 {
3269 if (rProp.Name == "TextPath")
3270 {
3271 rProp.Value >>= aTextPathSeq;
3272 for (const auto& rTextPathItem : std::as_const(aTextPathSeq))
3273 {
3274 if (rTextPathItem.Name == "TextPath")
3275 {
3276 rTextPathItem.Value >>= bResult;
3277 break;
3278 }
3279 }
3280 break;
3281 }
3282 }
3283 }
3284 }
3285 return bResult;
3286}
3287
3288void DrawingML::WriteText(const Reference<XInterface>& rXIface, bool bBodyPr, bool bText,
3289 sal_Int32 nXmlNamespace, bool bWritePropertiesAsLstStyles)
3290{
3291 // ToDo: Fontwork in DOCX
3292 uno::Reference<XText> xXText(rXIface, UNO_QUERY);
3293 if( !xXText.is() )
3294 return;
3295
3296 uno::Reference<drawing::XShape> xShape(rXIface, UNO_QUERY);
3297 uno::Reference<XPropertySet> rXPropSet(rXIface, UNO_QUERY);
3298
3299 constexpr const sal_Int32 constDefaultLeftRightInset = 254;
3300 constexpr const sal_Int32 constDefaultTopBottomInset = 127;
3301 sal_Int32 nLeft = constDefaultLeftRightInset;
3302 sal_Int32 nRight = constDefaultLeftRightInset;
3303 sal_Int32 nTop = constDefaultTopBottomInset;
3304 sal_Int32 nBottom = constDefaultTopBottomInset;
3305
3306 // top inset looks a bit different compared to ppt export
3307 // check if something related doesn't work as expected
3308 if (GetProperty(rXPropSet, "TextLeftDistance"))
3309 mAny >>= nLeft;
3310 if (GetProperty(rXPropSet, "TextRightDistance"))
3311 mAny >>= nRight;
3312 if (GetProperty(rXPropSet, "TextUpperDistance"))
3313 mAny >>= nTop;
3314 if (GetProperty(rXPropSet, "TextLowerDistance"))
3315 mAny >>= nBottom;
3316
3317 // Transform the text distance values so they are compatible with OOXML insets
3318 if (xShape.is())
3319 {
3320 sal_Int32 nTextHeight = xShape->getSize().Width;
3321
3322 auto* pCustomShape = dynamic_cast<SdrObjCustomShape*>(SdrObject::getSdrObjectFromXShape(xShape));
3323 if (pCustomShape)
3324 {
3325 tools::Rectangle aAnchorRect;
3326 pCustomShape->TakeTextAnchorRect(aAnchorRect);
3327 nTextHeight = aAnchorRect.GetSize().getHeight();
3328 }
3329
3330 if (nTop + nBottom >= nTextHeight)
3331 {
3332 sal_Int32 nDiff = std::abs(std::min(nTop, nBottom));
3333 nTop += nDiff;
3334 nBottom += nDiff;
3335 }
3336 }
3337
3338 std::optional<OString> sWritingMode;
3339 if (GetProperty(rXPropSet, "TextWritingMode"))
3340 {
3342 if( ( mAny >>= eMode ) && eMode == WritingMode_TB_RL )
3343 sWritingMode = "eaVert";
3344 }
3345 if (GetProperty(rXPropSet, "WritingMode"))
3346 {
3347 sal_Int16 nWritingMode;
3348 if (mAny >>= nWritingMode)
3349 {
3350 if (nWritingMode == text::WritingMode2::TB_RL)
3351 sWritingMode = "eaVert";
3352 else if (nWritingMode == text::WritingMode2::BT_LR)
3353 sWritingMode = "vert270";
3354 else if (nWritingMode == text::WritingMode2::TB_RL90)
3355 sWritingMode = "vert";
3356 else if (nWritingMode == text::WritingMode2::TB_LR)
3357 sWritingMode = "mongolianVert";
3358 }
3359 }
3360
3361 // read values from CustomShapeGeometry
3363 uno::Sequence<beans::PropertyValue> aTextPathSeq;
3364 bool bScaleX(false);
3365 OUString sShapeType("non-primitive");
3366 OUString sMSWordPresetTextWarp;
3367 sal_Int32 nTextPreRotateAngle = 0; // degree
3368 std::optional<Degree100> nTextRotateAngleDeg100; // text area rotation
3369
3370 if (GetProperty(rXPropSet, "CustomShapeGeometry"))
3371 {
3373 if ( mAny >>= aProps )
3374 {
3375 for ( const auto& rProp : std::as_const(aProps) )
3376 {
3377 if (rProp.Name == "TextPreRotateAngle")
3378 rProp.Value >>= nTextPreRotateAngle;
3379 else if (rProp.Name == "AdjustmentValues")
3380 rProp.Value >>= aAdjustmentSeq;
3381 else if (rProp.Name == "TextRotateAngle")
3382 {
3383 double fTextRotateAngle = 0; // degree
3384 rProp.Value >>= fTextRotateAngle;
3385 nTextRotateAngleDeg100 = Degree100(std::lround(fTextRotateAngle * 100.0));
3386 }
3387 else if (rProp.Name == "Type")
3388 rProp.Value >>= sShapeType;
3389 else if (rProp.Name == "TextPath")
3390 {
3391 rProp.Value >>= aTextPathSeq;
3392 for (const auto& rTextPathItem : std::as_const(aTextPathSeq))
3393 {
3394 if (rTextPathItem.Name == "ScaleX")
3395 rTextPathItem.Value >>= bScaleX;
3396 }
3397 }
3398 else if (rProp.Name == "PresetTextWarp")
3399 rProp.Value >>= sMSWordPresetTextWarp;
3400 }
3401 }
3402 }
3403 else
3404 {
3405 if (mpTextExport)
3406 {
3407 if (xShape)
3408 {
3409 auto xTextFrame = mpTextExport->GetUnoTextFrame(xShape);
3410 if (xTextFrame)
3411 {
3412 uno::Reference<beans::XPropertySet> xPropSet(xTextFrame, uno::UNO_QUERY);
3413 auto aAny = xPropSet->getPropertyValue("WritingMode");
3414 sal_Int16 nWritingMode;
3415 if (aAny >>= nWritingMode)
3416 {
3417 switch (nWritingMode)
3418 {
3419 case WritingMode2::TB_RL:
3420 sWritingMode = "eaVert";
3421 break;
3422 case WritingMode2::BT_LR:
3423 sWritingMode = "vert270";
3424 break;
3425 case WritingMode2::TB_RL90:
3426 sWritingMode = "vert";
3427 break;
3428 case WritingMode2::TB_LR:
3429 sWritingMode = "mongolianVert";
3430 break;
3431 default:
3432 break;
3433 }
3434 }
3435 }
3436 }
3437 }
3438 }
3439
3440 // read InteropGrabBag if any
3441 std::optional<OUString> sHorzOverflow;
3442 std::optional<OUString> sVertOverflow;
3443 bool bUpright = false;
3444 std::optional<OString> isUpright;
3445 if (rXPropSet->getPropertySetInfo()->hasPropertyByName("InteropGrabBag"))
3446 {
3447 uno::Sequence<beans::PropertyValue> aGrabBag;
3448 rXPropSet->getPropertyValue("InteropGrabBag") >>= aGrabBag;
3449 for (const auto& aProp : std::as_const(aGrabBag))
3450 {
3451 if (aProp.Name == "Upright")
3452 {
3453 aProp.Value >>= bUpright;
3454 isUpright = OString(bUpright ? "1" : "0");
3455 }
3456 else if (aProp.Name == "horzOverflow")
3457 {
3458 OUString sValue;
3459 aProp.Value >>= sValue;
3460 sHorzOverflow = sValue;
3461 }
3462 else if (aProp.Name == "vertOverflow")
3463 {
3464 OUString sValue;
3465 aProp.Value >>= sValue;
3466 sVertOverflow = sValue;
3467 }
3468 }
3469 }
3470
3471 bool bIsFontworkShape(IsFontworkShape(rXPropSet));
3472 OUString sPresetWarp(PresetGeometryTypeNames::GetMsoName(sShapeType));
3473 // ODF may have user defined TextPath, use "textPlain" as ersatz.
3474 if (sPresetWarp.isEmpty())
3475 sPresetWarp = bIsFontworkShape ? std::u16string_view(u"textPlain") : std::u16string_view(u"textNoShape");
3476
3477 bool bFromWordArt = !bScaleX
3478 && ( sPresetWarp == "textArchDown" || sPresetWarp == "textArchUp"
3479 || sPresetWarp == "textButton" || sPresetWarp == "textCircle");
3480
3481 if (bUpright)
3482 {
3483 Degree100 nShapeRotateAngleDeg100(0_deg100);
3484 if (GetProperty(rXPropSet, "RotateAngle"))
3485 nShapeRotateAngleDeg100 = Degree100(mAny.get<sal_Int32>());
3486 // Depending on shape rotation, the import has made 90deg changes to properties
3487 // "TextPreRotateAngle" and "TextRotateAngle". Revert it.
3488 bool bWasAngleChanged
3489 = (nShapeRotateAngleDeg100 > 4500_deg100 && nShapeRotateAngleDeg100 <= 13500_deg100)
3490 || (nShapeRotateAngleDeg100 > 22500_deg100
3491 && nShapeRotateAngleDeg100 <= 31500_deg100);
3492 if (bWasAngleChanged)
3493 {
3494 nTextRotateAngleDeg100 = nTextRotateAngleDeg100.value_or(0_deg100) + 9000_deg100;
3495 nTextPreRotateAngle -= 90;
3496 }
3497 // If text is no longer upright, user has changed something. Do not write 'upright' then.
3498 // This try to detect the case assumes, that the text area rotation was 0 in the original
3499 // MS Office document. That is likely because MS Office has no UI to set it and the
3500 // predefined SmartArt shapes, which use it, do not use 'upright'.
3501 Degree100 nAngleSum = nShapeRotateAngleDeg100 + nTextRotateAngleDeg100.value_or(0_deg100);
3502 if (abs(NormAngle18000(nAngleSum)) < 100_deg100) // consider inaccuracy from rounding
3503 {
3504 nTextRotateAngleDeg100.reset(); // 'upright' does not overrule text area rotation.
3505 }
3506 else
3507 {
3508 // User changes. Keep current angles.
3509 isUpright.reset();
3510 if (bWasAngleChanged)
3511 {
3512 nTextPreRotateAngle += 90;
3513 nTextRotateAngleDeg100 = nTextRotateAngleDeg100.value_or(0_deg100) - 9000_deg100;
3514 }
3515 }
3516 }
3517
3518 // ToDo: Unsure about this. Need to investigate shapes from diagram import, especially diagrams
3519 // with vertical text directions.
3520 if (nTextPreRotateAngle != 0 && !sWritingMode)
3521 {
3522 if (nTextPreRotateAngle == -90 || nTextPreRotateAngle == 270)
3523 sWritingMode = "vert";
3524 else if (nTextPreRotateAngle == -270 || nTextPreRotateAngle == 90)
3525 sWritingMode = "vert270";
3526 else if (nTextPreRotateAngle == -180 || nTextPreRotateAngle == 180)
3527 {
3528#if defined __GNUC__ && !defined __clang__ && __GNUC__ == 12
3529#pragma GCC diagnostic push
3530#pragma GCC diagnostic ignored "-Wmaybe-uninitialized"
3531#endif
3532 nTextRotateAngleDeg100
3533 = NormAngle18000(nTextRotateAngleDeg100.value_or(0_deg100) + 18000_deg100);
3534#if defined __GNUC__ && !defined __clang__ && __GNUC__ == 12
3535#pragma GCC diagnostic pop
3536#endif
3537 // ToDo: Examine insets. They might need rotation too. Check diagrams (SmartArt).
3538 }
3539 else
3540 SAL_WARN("oox", "unsuitable value for TextPreRotateAngle:" << nTextPreRotateAngle);
3541 }
3542 else if (nTextPreRotateAngle != 0 && sWritingMode && sWritingMode.value() == "eaVert")
3543 {
3544 // ToDo: eaVert plus 270deg clockwise rotation has to be written with vert="horz"
3545 // plus attribute 'normalEastAsianFlow="1"' on the <wps:wsp> element.
3546 }
3547 // else nothing to do
3548
3549 // Our WritingMode introduces text pre rotation which includes padding, MSO vert does not include
3550 // padding. Therefore set padding so, that is looks the same in MSO as in LO.
3551 if (sWritingMode)
3552 {
3553 if (sWritingMode.value() == "vert" || sWritingMode.value() == "eaVert")
3554 {
3555 sal_Int32 nHelp = nLeft;
3556 nLeft = nBottom;
3557 nBottom = nRight;
3558 nRight = nTop;
3559 nTop = nHelp;
3560 }
3561 else if (sWritingMode.value() == "vert270")
3562 {
3563 sal_Int32 nHelp = nLeft;
3564 nLeft = nTop;
3565 nTop = nRight;
3566 nRight = nBottom;
3567 nBottom = nHelp;
3568 }
3569 else if (sWritingMode.value() == "mongolianVert")
3570 {
3571 // ToDo: Examine padding
3572 }
3573 }
3574
3575
3576 std::optional<OString> sTextRotateAngleMSUnit;
3577 if (nTextRotateAngleDeg100.has_value())
3578#if defined __GNUC__ && !defined __clang__ && __GNUC__ == 12
3579#pragma GCC diagnostic push
3580#pragma GCC diagnostic ignored "-Wmaybe-uninitialized"
3581#endif
3582 sTextRotateAngleMSUnit
3583 = oox::drawingml::calcRotationValue(nTextRotateAngleDeg100.value().get());
3584#if defined __GNUC__ && !defined __clang__ && __GNUC__ == 12
3585#pragma GCC diagnostic pop
3586#endif
3587
3588 // Prepare attributes 'anchor' and 'anchorCtr'
3589 // LibreOffice has 12 value sets, MS Office only 6. We map them so, that it reverses the
3590 // 6 mappings from import, and we assign the others approximately.
3591 TextVerticalAdjust eVerticalAlignment(TextVerticalAdjust_TOP);
3592 if (GetProperty(rXPropSet, "TextVerticalAdjust"))
3593 mAny >>= eVerticalAlignment;
3594 TextHorizontalAdjust eHorizontalAlignment(TextHorizontalAdjust_CENTER);
3595 if (GetProperty(rXPropSet, "TextHorizontalAdjust"))
3596 mAny >>= eHorizontalAlignment;
3597
3598 const char* sAnchor = nullptr;
3599 bool bAnchorCtr = false;
3600 if (sWritingMode.has_value()
3601 && (sWritingMode.value() == "eaVert" || sWritingMode.value() == "mongolianVert"))
3602 {
3603 bAnchorCtr = eVerticalAlignment == TextVerticalAdjust_CENTER
3604 || eVerticalAlignment == TextVerticalAdjust_BOTTOM
3605 || eVerticalAlignment == TextVerticalAdjust_BLOCK;
3606 switch (eHorizontalAlignment)
3607 {
3608 case TextHorizontalAdjust_CENTER:
3609 sAnchor = "ctr";
3610 break;
3611 case TextHorizontalAdjust_LEFT:
3612 sAnchor = sWritingMode.value() == "eaVert" ? "b" : "t";
3613 break;
3614 case TextHorizontalAdjust_RIGHT:
3615 default: // TextHorizontalAdjust_BLOCK, should not happen
3616 sAnchor = sWritingMode.value() == "eaVert" ? "t" : "b";
3617 break;
3618 }
3619 }
3620 else
3621 {
3622 bAnchorCtr = eHorizontalAlignment == TextHorizontalAdjust_CENTER
3623 || eHorizontalAlignment == TextHorizontalAdjust_RIGHT;
3624 sAnchor = GetTextVerticalAdjust(eVerticalAlignment);
3625 }
3626
3627 bool bHasWrap = false;
3628 bool bWrap = false;
3629 // Only custom shapes obey the TextWordWrap option, normal text always wraps.
3630 if (dynamic_cast<SvxCustomShape*>(rXIface.get()) && GetProperty(rXPropSet, "TextWordWrap"))
3631 {
3632 mAny >>= bWrap;
3633 bHasWrap = true;
3634 }
3635
3636 if (bBodyPr)
3637 {
3638 const char* pWrap = (bHasWrap && !bWrap) || bIsFontworkShape ? "none" : nullptr;
3640 {
3641 // In case of DOCX, if we want to have the same effect as
3642 // TextShape's automatic word wrapping, then we need to set
3643 // wrapping to square.
3644 uno::Reference<lang::XServiceInfo> xServiceInfo(rXIface, uno::UNO_QUERY);
3645 if (xServiceInfo.is() && xServiceInfo->supportsService("com.sun.star.drawing.TextShape"))
3646 pWrap = "square";
3647 }
3648
3649 sal_Int16 nCols = 0;
3650 sal_Int32 nColSpacing = -1;
3651 if (GetProperty(rXPropSet, "TextColumns"))
3652 {
3653 if (css::uno::Reference<css::text::XTextColumns> xCols{ mAny, css::uno::UNO_QUERY })
3654 {
3655 nCols = xCols->getColumnCount();
3656 if (css::uno::Reference<css::beans::XPropertySet> xProps{ mAny,
3657 css::uno::UNO_QUERY })
3658 {
3659 if (GetProperty(xProps, "AutomaticDistance"))
3660 mAny >>= nColSpacing;
3661 }
3662 }
3663 }
3664
3665 mpFS->startElementNS( (nXmlNamespace ? nXmlNamespace : XML_a), XML_bodyPr,
3666 XML_numCol, sax_fastparser::UseIf(OString::number(nCols), nCols > 0),
3667 XML_spcCol, sax_fastparser::UseIf(OString::number(oox::drawingml::convertHmmToEmu(nColSpacing)), nCols > 0 && nColSpacing >= 0),
3668 XML_wrap, pWrap,
3669 XML_horzOverflow, sHorzOverflow,
3670 XML_vertOverflow, sVertOverflow,
3671 XML_fromWordArt, sax_fastparser::UseIf("1", bFromWordArt),
3672 XML_lIns, sax_fastparser::UseIf(OString::number(oox::drawingml::convertHmmToEmu(nLeft)), nLeft != constDefaultLeftRightInset),
3673 XML_rIns, sax_fastparser::UseIf(OString::number(oox::drawingml::convertHmmToEmu(nRight)), nRight != constDefaultLeftRightInset),
3674 XML_tIns, sax_fastparser::UseIf(OString::number(oox::drawingml::convertHmmToEmu(nTop)), nTop != constDefaultTopBottomInset),
3675 XML_bIns, sax_fastparser::UseIf(OString::number(oox::drawingml::convertHmmToEmu(nBottom)), nBottom != constDefaultTopBottomInset),
3676 XML_anchor, sAnchor,
3677 XML_anchorCtr, sax_fastparser::UseIf("1", bAnchorCtr),
3678 XML_vert, sWritingMode,
3679 XML_upright, isUpright,
3680 XML_rot, sTextRotateAngleMSUnit);
3681
3682 if (bIsFontworkShape)
3683 {
3684 if (aAdjustmentSeq.hasElements())
3685 {
3686 mpFS->startElementNS(XML_a, XML_prstTxWarp, XML_prst, sPresetWarp);
3687 mpFS->startElementNS(XML_a, XML_avLst);
3688 bool bHasTwoHandles(
3689 sPresetWarp == "textArchDownPour" || sPresetWarp == "textArchUpPour"
3690 || sPresetWarp == "textButtonPour" || sPresetWarp == "textCirclePour"
3691 || sPresetWarp == "textDoubleWave1" || sPresetWarp == "textWave1"
3692 || sPresetWarp == "textWave2" || sPresetWarp == "textWave4");
3693 for (sal_Int32 i = 0, nElems = aAdjustmentSeq.getLength(); i < nElems; ++i )
3694 {
3695 OString sName = "adj" + (bHasTwoHandles ? OString::number(i + 1) : OString());
3696 double fValue(0.0);
3697 if (aAdjustmentSeq[i].Value.getValueTypeClass() == TypeClass_DOUBLE)
3698 aAdjustmentSeq[i].Value >>= fValue;
3699 else
3700 {
3701 sal_Int32 nNumber(0);
3702 aAdjustmentSeq[i].Value >>= nNumber;
3703 fValue = static_cast<double>(nNumber);
3704 }
3705 // Convert from binary coordinate system with viewBox "0 0 21600 21600" and simple degree
3706 // to DrawingML with coordinate range 0..100000 and angle in 1/60000 degree.
3707 // Reverse to conversion in lcl_createPresetShape in drawingml/shape.cxx on import.
3708 if (sPresetWarp == "textArchDown" || sPresetWarp == "textArchUp"
3709 || sPresetWarp == "textButton" || sPresetWarp == "textCircle"
3710 || ((i == 0)
3711 && (sPresetWarp == "textArchDownPour" || sPresetWarp == "textArchUpPour"
3712 || sPresetWarp == "textButtonPour" || sPresetWarp == "textCirclePour")))
3713 {
3714 fValue *= 60000.0;
3715 if (fValue < 0)
3716 fValue += 21600000;
3717 }
3718 else if ((i == 1)
3719 && (sPresetWarp == "textDoubleWave1" || sPresetWarp == "textWave1"
3720 || sPresetWarp == "textWave2" || sPresetWarp == "textWave4"))
3721 {
3722 fValue = fValue / 0.216 - 50000.0;
3723 }
3724 else if ((i == 1)
3725 && (sPresetWarp == "textArchDownPour"
3726 || sPresetWarp == "textArchUpPour"
3727 || sPresetWarp == "textButtonPour"
3728 || sPresetWarp == "textCirclePour"))
3729 {
3730 fValue /= 0.108;
3731 }
3732 else
3733 {
3734 fValue /= 0.216;
3735 }
3736 OString sFmla = "val " + OString::number(std::lround(fValue));
3737 mpFS->singleElementNS(XML_a, XML_gd, XML_name, sName, XML_fmla, sFmla);
3738 // There exists faulty Favorite shapes with one handle but two adjustment values.
3739 if (!bHasTwoHandles)
3740 break;
3741 }
3742 mpFS->endElementNS(XML_a, XML_avLst);
3743 mpFS->endElementNS(XML_a, XML_prstTxWarp);
3744 }
3745 else
3746 {
3747 mpFS->singleElementNS(XML_a, XML_prstTxWarp, XML_prst, sPresetWarp);
3748 }
3749 }
3750 else if (GetDocumentType() == DOCUMENT_DOCX)
3751 {
3752 // interim solution for fdo#80897, roundtrip DOCX > LO > DOCX
3753 if (!sMSWordPresetTextWarp.isEmpty())
3754 mpFS->singleElementNS(XML_a, XML_prstTxWarp, XML_prst, sMSWordPresetTextWarp);
3755 }
3756
3758 {
3759 // tdf#112312: only custom shapes obey the TextAutoGrowHeight option
3760 bool bTextAutoGrowHeight = false;
3761 auto pSdrObjCustomShape = xShape.is() ? dynamic_cast<SdrObjCustomShape*>(SdrObject::getSdrObjectFromXShape(xShape)) : nullptr;
3762 if (pSdrObjCustomShape && GetProperty(rXPropSet, "TextAutoGrowHeight"))
3763 {
3764 mAny >>= bTextAutoGrowHeight;
3765 }
3766 mpFS->singleElementNS(XML_a, (bTextAutoGrowHeight ? XML_spAutoFit : XML_noAutofit));
3767 }
3769 {
3770 TextFitToSizeType eFit = TextFitToSizeType_NONE;
3771 if (GetProperty(rXPropSet, "TextFitToSize"))
3772 mAny >>= eFit;
3773
3774 if (eFit == TextFitToSizeType_AUTOFIT)
3775 {
3776 const sal_Int32 MAX_SCALE_VAL = 100000;
3777 sal_Int32 nFontScale = MAX_SCALE_VAL;
3778 SvxShapeText* pTextShape = dynamic_cast<SvxShapeText*>(rXIface.get());
3779 if (pTextShape)
3780 {
3781 SdrTextObj* pTextObject = dynamic_cast<SdrTextObj*>(pTextShape->GetSdrObject());
3782 if (pTextObject)
3783 nFontScale = pTextObject->GetFontScaleY() * 1000;
3784 }
3785
3786 mpFS->singleElementNS(XML_a, XML_normAutofit, XML_fontScale,
3787 sax_fastparser::UseIf(OString::number(nFontScale), nFontScale < MAX_SCALE_VAL && nFontScale > 0));
3788 }
3789 else
3790 {
3791 // tdf#127030: Only custom shapes obey the TextAutoGrowHeight option.
3792 bool bTextAutoGrowHeight = false;
3793 if (dynamic_cast<SvxCustomShape*>(rXIface.get()) && GetProperty(rXPropSet, "TextAutoGrowHeight"))
3794 mAny >>= bTextAutoGrowHeight;
3795 mpFS->singleElementNS(XML_a, (bTextAutoGrowHeight ? XML_spAutoFit : XML_noAutofit));
3796 }
3797 }
3798
3799 Write3DEffects( rXPropSet, /*bIsText=*/true );
3800
3801 mpFS->endElementNS((nXmlNamespace ? nXmlNamespace : XML_a), XML_bodyPr);
3802 }
3803
3804 Reference< XEnumerationAccess > access( xXText, UNO_QUERY );
3805 if( !access.is() || !bText )
3806 return;
3807
3808 Reference< XEnumeration > enumeration( access->createEnumeration() );
3809 if( !enumeration.is() )
3810 return;
3811
3812 SdrObject* pSdrObject = xShape.is() ? SdrObject::getSdrObjectFromXShape(xShape) : nullptr;
3813 const SdrTextObj* pTxtObj = dynamic_cast<SdrTextObj*>( pSdrObject );
3814 if (pTxtObj && mpTextExport)
3815 {
3817
3818 /*
3819 #i13885#
3820 When the object is actively being edited, that text is not set into
3821 the objects normal text object, but lives in a separate object.
3822 */
3823 if (pTxtObj->IsTextEditActive())
3824 {
3825 pParaObj = pTxtObj->CreateEditOutlinerParaObject();
3826 }
3827 else if (pTxtObj->GetOutlinerParaObject())
3828 pParaObj = *pTxtObj->GetOutlinerParaObject();
3829
3830 if (pParaObj)
3831 {
3832 // this is reached only in case some text is attached to the shape
3833 mpTextExport->WriteOutliner(*pParaObj);
3834 }
3835 return;
3836 }
3837
3838 bool bOverridingCharHeight = false;
3839 sal_Int32 nCharHeight = -1;
3840 bool bFirstParagraph = true;
3841
3842 // tdf#144092 For shapes without text: Export run properties (into
3843 // endParaRPr) from the shape's propset instead of the paragraph's.
3844 if(xXText->getString().isEmpty() && enumeration->hasMoreElements())
3845 {
3846 Any aAny (enumeration->nextElement());
3847 Reference<XTextContent> xParagraph;
3848 if( aAny >>= xParagraph )
3849 {
3850 mpFS->startElementNS(XML_a, XML_p);
3851 WriteParagraphProperties(xParagraph, nCharHeight, XML_pPr);
3852 sal_Int16 nDummy = -1;
3853 WriteRunProperties(rXPropSet, false, XML_endParaRPr, false,
3854 bOverridingCharHeight, nCharHeight, nDummy, rXPropSet);
3855 mpFS->endElementNS(XML_a, XML_p);
3856 }
3857 return;
3858 }
3859
3860 while( enumeration->hasMoreElements() )
3861 {
3863 Any any ( enumeration->nextElement() );
3864
3865 if( any >>= paragraph)
3866 {
3867 if (bFirstParagraph && bWritePropertiesAsLstStyles)
3868 WriteLstStyles(paragraph, bOverridingCharHeight, nCharHeight, rXPropSet);
3869
3870 WriteParagraph(paragraph, bOverridingCharHeight, nCharHeight, rXPropSet);
3871 bFirstParagraph = false;
3872 }
3873 }
3874}
3876void DrawingML::WritePresetShape( const OString& pShape , std::vector< std::pair<sal_Int32,sal_Int32>> & rAvList )
3877{
3878 mpFS->startElementNS(XML_a, XML_prstGeom, XML_prst, pShape);
3879 if ( !rAvList.empty() )
3880 {
3881
3882 mpFS->startElementNS(XML_a, XML_avLst);
3883 for (auto const& elem : rAvList)
3884 {
3885 OString sName = "adj" + ( ( elem.first > 0 ) ? OString::number(elem.first) : OString() );
3886 OString sFmla = "val " + OString::number( elem.second );
3887
3888 mpFS->singleElementNS(XML_a, XML_gd, XML_name, sName, XML_fmla, sFmla);
3889 }
3890 mpFS->endElementNS( XML_a, XML_avLst );
3891 }
3892 else
3893 mpFS->singleElementNS(XML_a, XML_avLst);
3894
3895 mpFS->endElementNS( XML_a, XML_prstGeom );
3896}
3898void DrawingML::WritePresetShape( const OString& pShape )
3899{
3900 mpFS->startElementNS(XML_a, XML_prstGeom, XML_prst, pShape);
3901 mpFS->singleElementNS(XML_a, XML_avLst);
3902 mpFS->endElementNS( XML_a, XML_prstGeom );
3903}
3905static std::map< OString, std::vector<OString> > lcl_getAdjNames()
3906{
3907 std::map< OString, std::vector<OString> > aRet;
3908
3909 OUString aPath("$BRAND_BASE_DIR/" LIBO_SHARE_FOLDER "/filter/oox-drawingml-adj-names");
3910 rtl::Bootstrap::expandMacros(aPath);
3911 SvFileStream aStream(aPath, StreamMode::READ);
3912 if (aStream.GetError() != ERRCODE_NONE)
3913 SAL_WARN("oox.shape", "failed to open oox-drawingml-adj-names");
3914 OStringBuffer aLine;
3915 bool bNotDone = aStream.ReadLine(aLine);
3916 while (bNotDone)
3917 {
3918 sal_Int32 nIndex = 0;
3919 // Each line is in a "key\tvalue" format: read the key, the rest is the value.
3920 OString aKey( o3tl::getToken(aLine, 0, '\t', nIndex) );
3921 OString aValue( std::string_view(aLine).substr(nIndex) );
3922 aRet[aKey].push_back(aValue);
3923 bNotDone = aStream.ReadLine(aLine);
3924 }
3925 return aRet;
3926}
3927
3928void DrawingML::WritePresetShape( const OString& pShape, MSO_SPT eShapeType, bool bPredefinedHandlesUsed, const PropertyValue& rProp )
3929{
3930 static std::map< OString, std::vector<OString> > aAdjMap = lcl_getAdjNames();
3931 // If there are predefined adj names for this shape type, look them up now.
3932 std::vector<OString> aAdjustments;
3933 if (aAdjMap.find(pShape) != aAdjMap.end())
3934 aAdjustments = aAdjMap[pShape];
3935
3936 mpFS->startElementNS(XML_a, XML_prstGeom, XML_prst, pShape);
3937 mpFS->startElementNS(XML_a, XML_avLst);
3938
3939 Sequence< drawing::EnhancedCustomShapeAdjustmentValue > aAdjustmentSeq;
3940 if ( ( rProp.Value >>= aAdjustmentSeq )
3941 && eShapeType != mso_sptActionButtonForwardNext // we have adjustments values for these type of shape, but MSO doesn't like them
3942 && eShapeType != mso_sptActionButtonBackPrevious // so they are now disabled
3943 && pShape != "rect" //some shape types are commented out in pCustomShapeTypeTranslationTable[] & are being defaulted to rect & rect does not have adjustment values/name.
3944 )
3945 {
3946 SAL_INFO("oox.shape", "adj seq len: " << aAdjustmentSeq.getLength());
3947 sal_Int32 nAdjustmentsWhichNeedsToBeConverted = 0;
3948 if ( bPredefinedHandlesUsed )
3949 EscherPropertyContainer::LookForPolarHandles( eShapeType, nAdjustmentsWhichNeedsToBeConverted );
3950
3951 sal_Int32 nValue, nLength = aAdjustmentSeq.getLength();
3952 // 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.
3953 // Sometimes there are more values than needed, so we ignore the excessive ones.
3954 if (aAdjustments.size() <= o3tl::make_unsigned(nLength))
3955 {
3956 for (sal_Int32 i = 0; i < static_cast<sal_Int32>(aAdjustments.size()); i++)
3957 {
3958 if( EscherPropertyContainer::GetAdjustmentValue( aAdjustmentSeq[ i ], i, nAdjustmentsWhichNeedsToBeConverted, nValue ) )
3959 {
3960 // If the document model doesn't have an adjustment name (e.g. shape was created from VML), then take it from the predefined list.
3961 OString aAdjName = aAdjustmentSeq[i].Name.isEmpty()
3962 ? aAdjustments[i]
3963 : aAdjustmentSeq[i].Name.toUtf8();
3964
3965 mpFS->singleElementNS( XML_a, XML_gd,
3966 XML_name, aAdjName,
3967 XML_fmla, "val " + OString::number(nValue));
3968 }
3969 }
3970 }
3971 }
3972
3973 mpFS->endElementNS( XML_a, XML_avLst );
3974 mpFS->endElementNS( XML_a, XML_prstGeom );
3975}
3976
3977namespace // helpers for DrawingML::WriteCustomGeometry
3978{
3979sal_Int32
3980FindNextCommandEndSubpath(const sal_Int32 nStart,
3981 const uno::Sequence<drawing::EnhancedCustomShapeSegment>& rSegments)
3982{
3983 sal_Int32 i = nStart < 0 ? 0 : nStart;
3984 while (i < rSegments.getLength() && rSegments[i].Command != ENDSUBPATH)
3985 i++;
3986 return i;
3987}
3988
3989bool HasCommandInSubPath(const sal_Int16 nCommand, const sal_Int32 nFirst, const sal_Int32 nLast,
3990 const uno::Sequence<drawing::EnhancedCustomShapeSegment>& rSegments)
3991{
3992 for (sal_Int32 i = nFirst < 0 ? 0 : nFirst; i <= nLast && i < rSegments.getLength(); i++)
3993 {
3994 if (rSegments[i].Command == nCommand)
3995 return true;
3996 }
3997 return false;
3998}
3999
4000// Ellipse is given by radii fwR and fhR and center (fCx|fCy). The ray from center through point RayP
4001// intersects the ellipse in point S and this point S has angle fAngleDeg in degrees.
4002void getEllipsePointAndAngleFromRayPoint(double& rfAngleDeg, double& rfSx, double& rfSy,
4003 const double fWR, const double fHR, const double fCx,
4004 const double fCy, const double fRayPx, const double fRayPy)
4005{
4007 {
4008 rfSx = fCx; // needed for getting new 'current point'
4009 rfSy = fCy;
4010 }
4011 else
4012 {
4013 // center ellipse at origin, stretch in y-direction to circle, flip to Math orientation
4014 // and get angle
4015 double fCircleMathAngle = atan2(-fWR / fHR * (fRayPy - fCy), fRayPx - fCx);
4016 // use angle for intersection point on circle and stretch back to ellipse
4017 double fPointMathEllipse_x = fWR * cos(fCircleMathAngle);
4018 double fPointMathEllipse_y = fHR * sin(fCircleMathAngle);
4019 // get angle of intersection point on ellipse
4020 double fEllipseMathAngle = atan2(fPointMathEllipse_y, fPointMathEllipse_x);
4021 // convert from Math to View orientation and shift ellipse back from origin
4022 rfAngleDeg = -basegfx::rad2deg(fEllipseMathAngle);
4023 rfSx = fPointMathEllipse_x + fCx;
4024 rfSy = -fPointMathEllipse_y + fCy;
4025 }
4026}
4027
4028void getEllipsePointFromViewAngle(double& rfSx, double& rfSy, const double fWR, const double fHR,
4029 const double fCx, const double fCy, const double fViewAngleDeg)
4030{
4032 {
4033 rfSx = fCx; // needed for getting new 'current point'
4034 rfSy = fCy;
4035 }
4036 else
4037 {
4038 double fX = cos(basegfx::deg2rad(fViewAngleDeg)) / fWR;
4039 double fY = sin(basegfx::deg2rad(fViewAngleDeg)) / fHR;
4040 double fRadius = 1.0 / std::hypot(fX, fY);
4041 rfSx = fCx + fRadius * cos(basegfx::deg2rad(fViewAngleDeg));
4042 rfSy = fCy + fRadius * sin(basegfx::deg2rad(fViewAngleDeg));
4043 }
4044}
4045
4046sal_Int32 GetCustomGeometryPointValue(const css::drawing::EnhancedCustomShapeParameter& rParam,
4047 const EnhancedCustomShape2d& rCustomShape2d,
4048 const bool bReplaceGeoWidth, const bool bReplaceGeoHeight)
4049{
4050 double fValue = 0.0;
4051 rCustomShape2d.GetParameter(fValue, rParam, bReplaceGeoWidth, bReplaceGeoHeight);
4052 sal_Int32 nValue(std::lround(fValue));
4053
4054 return nValue;
4055}
4056
4057struct TextAreaRect
4059 OString left;
4060 OString top;
4061 OString right;
4062 OString bottom;
4063};
4064
4065struct Guide
4067 OString sName;
4068 OString sFormula;
4069};
4070
4071void prepareTextArea(const EnhancedCustomShape2d& rEnhancedCustomShape2d,
4072 std::vector<Guide>& rGuideList, TextAreaRect& rTextAreaRect)
4073{
4074 tools::Rectangle aTextAreaLO(rEnhancedCustomShape2d.GetTextRect());
4075 tools::Rectangle aLogicRectLO(rEnhancedCustomShape2d.GetLogicRect());
4076 if (aTextAreaLO == aLogicRectLO)
4077 {
4078 rTextAreaRect.left = "l";
4079 rTextAreaRect.top = "t";
4080 rTextAreaRect.right = "r";
4081 rTextAreaRect.bottom = "b";
4082 return;
4083 }
4084 // Flip aTextAreaLO if shape is flipped
4085 if (rEnhancedCustomShape2d.IsFlipHorz())
4086 aTextAreaLO.Move((aLogicRectLO.Center().X() - aTextAreaLO.Center().X()) * 2, 0);
4087 if (rEnhancedCustomShape2d.IsFlipVert())
4088 aTextAreaLO.Move(0, (aLogicRectLO.Center().Y() - aTextAreaLO.Center().Y()) * 2);
4089
4090 Guide aGuide;
4091 // horizontal
4092 const sal_Int32 nWidth = aLogicRectLO.Right() - aLogicRectLO.Left();
4093 const OString sWidth = OString::number(oox::drawingml::convertHmmToEmu(nWidth));
4094
4095 // left
4096 aGuide.sName = "textAreaLeft";
4097 sal_Int32 nHelp = aTextAreaLO.Left() - aLogicRectLO.Left();
4098 const OString sLeft = OString::number(oox::drawingml::convertHmmToEmu(nHelp));
4099 aGuide.sFormula = "*/ " + sLeft + " w " + sWidth;
4100 rTextAreaRect.left = aGuide.sName;
4101 rGuideList.push_back(aGuide);
4102
4103 // right
4104 aGuide.sName = "textAreaRight";
4105 nHelp = aTextAreaLO.Right() - aLogicRectLO.Left();
4106 const OString sRight = OString::number(oox::drawingml::convertHmmToEmu(nHelp));
4107 aGuide.sFormula = "*/ " + sRight + " w " + sWidth;
4108 rTextAreaRect.right = aGuide.sName;
4109 rGuideList.push_back(aGuide);
4110
4111 // vertical
4112 const sal_Int32 nHeight = aLogicRectLO.Bottom() - aLogicRectLO.Top();
4113 const OString sHeight = OString::number(oox::drawingml::convertHmmToEmu(nHeight));
4114
4115 // top
4116 aGuide.sName = "textAreaTop";
4117 nHelp = aTextAreaLO.Top() - aLogicRectLO.Top();
4118 const OString sTop = OString::number(oox::drawingml::convertHmmToEmu(nHelp));
4119 aGuide.sFormula = "*/ " + sTop + " h " + sHeight;
4120 rTextAreaRect.top = aGuide.sName;
4121 rGuideList.push_back(aGuide);
4122
4123 // bottom
4124 aGuide.sName = "textAreaBottom";
4125 nHelp = aTextAreaLO.Bottom() - aLogicRectLO.Top();
4126 const OString sBottom = OString::number(oox::drawingml::convertHmmToEmu(nHelp));
4127 aGuide.sFormula = "*/ " + sBottom + " h " + sHeight;
4128 rTextAreaRect.bottom = aGuide.sName;
4129 rGuideList.push_back(aGuide);
4130
4131 return;
4132}
4133}
4136 const Reference< XShape >& rXShape,
4137 const SdrObjCustomShape& rSdrObjCustomShape)
4138{
4139 uno::Reference< beans::XPropertySet > aXPropSet;
4140 uno::Any aAny( rXShape->queryInterface(cppu::UnoType<beans::XPropertySet>::get()));
4141
4142 if ( ! (aAny >>= aXPropSet) )
4143 return false;
4144
4145 try
4146 {
4147 aAny = aXPropSet->getPropertyValue( "CustomShapeGeometry" );
4148 if ( !aAny.hasValue() )
4149 return false;
4150 }
4151 catch( const ::uno::Exception& )
4152 {
4153 return false;
4154 }
4155
4156 auto pGeometrySeq = o3tl::tryAccess<uno::Sequence<beans::PropertyValue>>(aAny);
4157 if (!pGeometrySeq)
4158 return false;
4159
4160 auto pPathProp = std::find_if(std::cbegin(*pGeometrySeq), std::cend(*pGeometrySeq),
4161 [](const PropertyValue& rProp) { return rProp.Name == "Path"; });
4162 if (pPathProp == std::cend(*pGeometrySeq))
4163 return false;
4164
4165 uno::Sequence<beans::PropertyValue> aPathProp;
4166 pPathProp->Value >>= aPathProp;
4167
4168 uno::Sequence<drawing::EnhancedCustomShapeParameterPair> aPairs;
4169 uno::Sequence<drawing::EnhancedCustomShapeSegment> aSegments;
4170 uno::Sequence<awt::Size> aPathSize;
4171 bool bReplaceGeoWidth = false;
4172 bool bReplaceGeoHeight = false;
4173 for (const beans::PropertyValue& rPathProp : std::as_const(aPathProp))
4174 {
4175 if (rPathProp.Name == "Coordinates")
4176 rPathProp.Value >>= aPairs;
4177 else if (rPathProp.Name == "Segments")
4178 rPathProp.Value >>= aSegments;
4179 else if (rPathProp.Name == "SubViewSize")
4180 rPathProp.Value >>= aPathSize;
4181 else if (rPathProp.Name == "StretchX")
4182 bReplaceGeoWidth = true;
4183 else if (rPathProp.Name == "StretchY")
4184 bReplaceGeoHeight = true;
4185 }
4186
4187 if ( !aPairs.hasElements() )
4188 return false;
4189
4190 if ( !aSegments.hasElements() )
4191 {
4192 aSegments = uno::Sequence<drawing::EnhancedCustomShapeSegment>
4193 {
4194 { MOVETO, 1 },
4195 { LINETO,
4196 static_cast<sal_Int16>(std::min( aPairs.getLength() - 1, sal_Int32(32767) )) },
4197 { CLOSESUBPATH, 0 },
4198 { ENDSUBPATH, 0 }
4199 };
4200 };
4201
4202 int nExpectedPairCount = std::accumulate(std::cbegin(aSegments), std::cend(aSegments), 0,
4203 [](const int nSum, const drawing::EnhancedCustomShapeSegment& rSegment) { return nSum + rSegment.Count; });
4204
4205 if ( nExpectedPairCount > aPairs.getLength() )
4206 {
4207 SAL_WARN("oox.shape", "Segments need " << nExpectedPairCount << " coordinates, but Coordinates have only " << aPairs.getLength() << " pairs.");
4208 return false;
4209 }
4210
4211 // A EnhancedCustomShape2d caches the equation results. Therefore we use only one of it for the
4212 // entire method.
4213 const EnhancedCustomShape2d aCustomShape2d(const_cast<SdrObjCustomShape&>(rSdrObjCustomShape));
4214
4215 TextAreaRect aTextAreaRect;
4216 std::vector<Guide> aGuideList; // for now only for <a:rect>
4217 prepareTextArea(aCustomShape2d, aGuideList, aTextAreaRect);
4218 mpFS->startElementNS(XML_a, XML_custGeom);
4219 mpFS->singleElementNS(XML_a, XML_avLst);
4220 if (aGuideList.empty())
4221 {
4222 mpFS->singleElementNS(XML_a, XML_gdLst);
4223 }
4224 else
4225 {
4226 mpFS->startElementNS(XML_a, XML_gdLst);
4227 for (auto const& elem : aGuideList)
4228 {
4229 mpFS->singleElementNS(XML_a, XML_gd, XML_name, elem.sName, XML_fmla, elem.sFormula);
4230 }
4231 mpFS->endElementNS(XML_a, XML_gdLst);
4232 }
4233 mpFS->singleElementNS(XML_a, XML_ahLst);
4234 mpFS->singleElementNS(XML_a, XML_rect, XML_l, aTextAreaRect.left, XML_t, aTextAreaRect.top,
4235 XML_r, aTextAreaRect.right, XML_b, aTextAreaRect.bottom);
4236 mpFS->startElementNS(XML_a, XML_pathLst);
4237
4238 // Prepare width and height for <a:path>
4239 bool bUseGlobalViewBox(false);
4240
4241 // nViewBoxWidth must be integer otherwise ReplaceGeoWidth in aCustomShape2d.GetParameter() is not
4242 // triggered; same for height.
4243 sal_Int32 nViewBoxWidth(0);
4244 sal_Int32 nViewBoxHeight(0);
4245 if (!aPathSize.hasElements())
4246 {
4247 bUseGlobalViewBox = true;
4248 // If draw:viewBox is missing in draw:enhancedGeometry, then import sets
4249 // viewBox="0 0 21600 21600". Missing ViewBox can only occur, if user has manipulated
4250 // current file via macro. Author of macro has to fix it.
4251 auto pProp = std::find_if(
4252 std::cbegin(*pGeometrySeq), std::cend(*pGeometrySeq),
4253 [](const beans::PropertyValue& rGeomProp) { return rGeomProp.Name == "ViewBox"; });
4254 if (pProp != std::cend(*pGeometrySeq))
4255 {
4256 css::awt::Rectangle aViewBox;
4257 if (pProp->Value >>= aViewBox)
4258 {
4259 nViewBoxWidth = aViewBox.Width;
4260 nViewBoxHeight = aViewBox.Height;
4261 css::drawing::EnhancedCustomShapeParameter aECSP;
4262 aECSP.Type = css::drawing::EnhancedCustomShapeParameterType::NORMAL;
4263 aECSP.Value <<= nViewBoxWidth;
4264 double fRetValue;
4265 aCustomShape2d.GetParameter(fRetValue, aECSP, true, false);
4266 nViewBoxWidth = basegfx::fround(fRetValue);
4267 aECSP.Value <<= nViewBoxHeight;
4268 aCustomShape2d.GetParameter(fRetValue, aECSP, false, true);
4269 nViewBoxHeight = basegfx::fround(fRetValue);
4270 }
4271 }
4272 // Import from oox or documents, which are imported from oox and saved to strict ODF, might
4273 // have no subViewSize but viewBox="0 0 0 0". We need to generate width and height in those
4274 // cases. Even if that is fixed, we need the substitute for old documents.
4275 if ((nViewBoxWidth == 0 && nViewBoxHeight == 0) || pProp == std::cend(*pGeometrySeq))
4276 {
4277 // Generate a substitute based on point coordinates
4278 sal_Int32 nXMin(0);
4279 aPairs[0].First.Value >>= nXMin;
4280 sal_Int32 nXMax = nXMin;
4281 sal_Int32 nYMin(0);
4282 aPairs[0].Second.Value >>= nYMin;
4283 sal_Int32 nYMax = nYMin;
4284
4285 for (const auto& rPair : std::as_const(aPairs))
4286 {
4287 sal_Int32 nX = GetCustomGeometryPointValue(rPair.First, aCustomShape2d,
4288 bReplaceGeoWidth, false);
4289 sal_Int32 nY = GetCustomGeometryPointValue(rPair.Second, aCustomShape2d, false,
4290 bReplaceGeoHeight);
4291 if (nX < nXMin)
4292 nXMin = nX;
4293 if (nY < nYMin)
4294 nYMin = nY;
4295 if (nX > nXMax)
4296 nXMax = nX;
4297 if (nY > nYMax)
4298 nYMax = nY;
4299 }
4300 nViewBoxWidth = std::max(nXMax, nXMax - nXMin);
4301 nViewBoxHeight = std::max(nYMax, nYMax - nYMin);
4302 }
4303 // ToDo: Other values of left,top than 0,0 are not considered yet. Such would require a
4304 // shift of the resulting path coordinates.
4305 }
4306
4307 // Iterate over subpaths
4308 sal_Int32 nPairIndex = 0; // index over "Coordinates"
4309 sal_Int32 nPathSizeIndex = 0; // index over "SubViewSize"
4310 sal_Int32 nSubpathStartIndex(0); // index over "Segments"
4311 sal_Int32 nSubPathIndex(0); // serial number of current subpath
4312 do
4313 {
4314 bool bOK(true); // catch faulty paths were commands do not correspond to points
4315 // get index of next command ENDSUBPATH; if such doesn't exist use index behind last segment
4316 sal_Int32 nNextNcommandIndex = FindNextCommandEndSubpath(nSubpathStartIndex, aSegments);
4317
4318 // Prepare attributes for a:path start element
4319 // NOFILL or one of the LIGHTEN commands
4320 std::optional<OString> sFill;
4321 if (HasCommandInSubPath(NOFILL, nSubpathStartIndex, nNextNcommandIndex - 1, aSegments))
4322 sFill = "none";
4323 else if (HasCommandInSubPath(DARKEN, nSubpathStartIndex, nNextNcommandIndex - 1, aSegments))
4324 sFill = "darken";
4325 else if (HasCommandInSubPath(DARKENLESS, nSubpathStartIndex, nNextNcommandIndex - 1,
4326 aSegments))
4327 sFill = "darkenLess";
4328 else if (HasCommandInSubPath(LIGHTEN, nSubpathStartIndex, nNextNcommandIndex - 1,
4329 aSegments))
4330 sFill = "lighten";
4331 else if (HasCommandInSubPath(LIGHTENLESS, nSubpathStartIndex, nNextNcommandIndex - 1,
4332 aSegments))
4333 sFill = "lightenLess";
4334 else
4335 {
4336 // shading info might be in object type, e.g. "Octagon Bevel".
4337 sal_Int32 nLuminanceChange(aCustomShape2d.GetLuminanceChange(nSubPathIndex));
4338 if (nLuminanceChange <= -40)
4339 sFill = "darken";
4340 else if (nLuminanceChange <= -10)
4341 sFill = "darkenLess";
4342 else if (nLuminanceChange >= 40)
4343 sFill = "lighten";
4344 else if (nLuminanceChange >= 10)
4345 sFill = "lightenLess";
4346 }
4347 // NOSTROKE
4348 std::optional<OString> sStroke;
4349 if (HasCommandInSubPath(NOSTROKE, nSubpathStartIndex, nNextNcommandIndex - 1, aSegments))
4350 sStroke = "0";
4351
4352 // Write a:path start element
4353 mpFS->startElementNS(
4354 XML_a, XML_path, XML_fill, sFill, XML_stroke, sStroke, XML_w,
4355 OString::number(bUseGlobalViewBox ? nViewBoxWidth : aPathSize[nPathSizeIndex].Width),
4356 XML_h,
4357 OString::number(bUseGlobalViewBox ? nViewBoxHeight : aPathSize[nPathSizeIndex].Height));
4358
4359 // Arcs drawn by commands ELLIPTICALQUADRANTX and ELLIPTICALQUADRANTY depend on the position
4360 // of the target point in regard to the current point. Therefore we need to track the
4361 // current point. A current point is not defined in the beginning.
4362 double fCurrentX(0.0);
4363 double fCurrentY(0.0);
4364 bool bCurrentValid(false);
4365 // Actually write the subpath
4366 for (sal_Int32 nSegmentIndex = nSubpathStartIndex; nSegmentIndex < nNextNcommandIndex;
4367 ++nSegmentIndex)
4368 {
4369 const auto& rSegment(aSegments[nSegmentIndex]);
4370 if (rSegment.Command == CLOSESUBPATH)
4371 {
4372 mpFS->singleElementNS(XML_a, XML_close); // command Z has no parameter
4373 // ODF 1.4 specifies, that the start of the subpath becomes the current point.
4374 // But that is not implemented yet. Currently LO keeps the last current point.
4375 }
4376 for (sal_Int32 k = 0; k < rSegment.Count && bOK; ++k)
4377 {
4378 bOK = WriteCustomGeometrySegment(rSegment.Command, k, aPairs, nPairIndex, fCurrentX,
4379 fCurrentY, bCurrentValid, aCustomShape2d,
4380 bReplaceGeoWidth, bReplaceGeoHeight);
4381 }
4382 } // end loop over all commands of subpath
4383 // finish this subpath in any case
4384 mpFS->endElementNS(XML_a, XML_path);
4385
4386 if (!bOK)
4387 break; // exit loop if not enough values in aPairs
4388
4389 // step forward to next subpath
4390 nSubpathStartIndex = nNextNcommandIndex + 1;
4391 nPathSizeIndex++;
4392 nSubPathIndex++;
4393 } while (nSubpathStartIndex < aSegments.getLength());
4394
4395 mpFS->endElementNS(XML_a, XML_pathLst);
4396 mpFS->endElementNS(XML_a, XML_custGeom);
4397 return true; // We have written custGeom even if path is poorly structured.
4398}
4401 const sal_Int16 eCommand, const sal_Int32 nCount,
4402 const uno::Sequence<css::drawing::EnhancedCustomShapeParameterPair>& rPairs,
4403 sal_Int32& rnPairIndex, double& rfCurrentX, double& rfCurrentY, bool& rbCurrentValid,
4404 const EnhancedCustomShape2d& rCustomShape2d, const bool bReplaceGeoWidth,
4405 const bool bReplaceGeoHeight)
4406{
4407 switch (eCommand)
4408 {
4409 case MOVETO:
4410 {
4411 if (rnPairIndex >= rPairs.getLength())
4412 return false;
4413
4414 mpFS->startElementNS(XML_a, XML_moveTo);
4415 WriteCustomGeometryPoint(rPairs[rnPairIndex], rCustomShape2d, bReplaceGeoWidth,
4416 bReplaceGeoHeight);
4417 mpFS->endElementNS(XML_a, XML_moveTo);
4418 rCustomShape2d.GetParameter(rfCurrentX, rPairs[rnPairIndex].First, bReplaceGeoWidth,
4419 false);
4420 rCustomShape2d.GetParameter(rfCurrentY, rPairs[rnPairIndex].Second, false,
4421 bReplaceGeoHeight);
4422 rbCurrentValid = true;
4423 rnPairIndex++;
4424 break;
4425 }
4426 case LINETO:
4427 {
4428 if (rnPairIndex >= rPairs.getLength())
4429 return false;
4430 // LINETO without valid current point is a faulty path. LO is tolerant and makes a
4431 // moveTo instead. Do the same on export. MS OFFICE requires a current point for lnTo,
4432 // otherwise it shows nothing of the shape.
4433 if (rbCurrentValid)
4434 {
4435 mpFS->startElementNS(XML_a, XML_lnTo);
4436 WriteCustomGeometryPoint(rPairs[rnPairIndex], rCustomShape2d, bReplaceGeoWidth,
4437 bReplaceGeoHeight);
4438 mpFS->endElementNS(XML_a, XML_lnTo);
4439 }
4440 else
4441 {
4442 mpFS->startElementNS(XML_a, XML_moveTo);
4443 WriteCustomGeometryPoint(rPairs[rnPairIndex], rCustomShape2d, bReplaceGeoWidth,
4444 bReplaceGeoHeight);
4445 mpFS->endElementNS(XML_a, XML_moveTo);
4446 }
4447 rCustomShape2d.GetParameter(rfCurrentX, rPairs[rnPairIndex].First, bReplaceGeoWidth,
4448 false);
4449 rCustomShape2d.GetParameter(rfCurrentY, rPairs[rnPairIndex].Second, false,
4450 bReplaceGeoHeight);
4451 rbCurrentValid = true;
4452 rnPairIndex++;
4453 break;
4454 }
4455 case CURVETO:
4456 {
4457 if (rnPairIndex + 2 >= rPairs.getLength())
4458 return false;
4459
4460 mpFS->startElementNS(XML_a, XML_cubicBezTo);
4461 for (sal_uInt8 i = 0; i <= 2; ++i)
4462 {
4463 WriteCustomGeometryPoint(rPairs[rnPairIndex + i], rCustomShape2d, bReplaceGeoWidth,
4464 bReplaceGeoHeight);
4465 }
4466 mpFS->endElementNS(XML_a, XML_cubicBezTo);
4467 rCustomShape2d.GetParameter(rfCurrentX, rPairs[rnPairIndex + 2].First, bReplaceGeoWidth,
4468 false);
4469 rCustomShape2d.GetParameter(rfCurrentY, rPairs[rnPairIndex + 2].Second, false,
4470 bReplaceGeoHeight);
4471 rbCurrentValid = true;
4472 rnPairIndex += 3;
4473 break;
4474 }
4475 case ANGLEELLIPSETO:
4476 case ANGLEELLIPSE:
4477 {
4478 if (rnPairIndex + 2 >= rPairs.getLength())
4479 return false;
4480
4481 // Read parameters
4482 double fCx = 0.0;
4483 rCustomShape2d.GetParameter(fCx, rPairs[rnPairIndex].First, bReplaceGeoWidth, false);
4484 double fCy = 0.0;
4485 rCustomShape2d.GetParameter(fCy, rPairs[rnPairIndex].Second, false, bReplaceGeoHeight);
4486 double fWR = 0.0;
4487 rCustomShape2d.GetParameter(fWR, rPairs[rnPairIndex + 1].First, false, false);
4488 double fHR = 0.0;
4489 rCustomShape2d.GetParameter(fHR, rPairs[rnPairIndex + 1].Second, false, false);
4490 double fStartAngle = 0.0;
4491 rCustomShape2d.GetParameter(fStartAngle, rPairs[rnPairIndex + 2].First, false, false);
4492 double fEndAngle = 0.0;
4493 rCustomShape2d.GetParameter(fEndAngle, rPairs[rnPairIndex + 2].Second, false, false);
4494
4495 // Prepare start and swing angle
4496 sal_Int32 nStartAng(std::lround(fStartAngle * 60000));
4497 sal_Int32 nSwingAng = 0;
4498 if (basegfx::fTools::equalZero(fStartAngle)
4499 && basegfx::fTools::equalZero(fEndAngle - 360.0))
4500 nSwingAng = 360 * 60000; // special case full circle
4501 else
4502 {
4503 nSwingAng = std::lround((fEndAngle - fStartAngle) * 60000);
4504 if (nSwingAng < 0)
4505 nSwingAng += 360 * 60000;
4506 }
4507
4508 // calculate start point on ellipse
4509 double fSx = 0.0;
4510 double fSy = 0.0;
4511 getEllipsePointFromViewAngle(fSx, fSy, fWR, fHR, fCx, fCy, fStartAngle);
4512
4513 // write markup for going to start point
4514 // lnTo requires a valid current point
4515 if (eCommand == ANGLEELLIPSETO && rbCurrentValid)
4516 {
4517 mpFS->startElementNS(XML_a, XML_lnTo);
4518 mpFS->singleElementNS(XML_a, XML_pt, XML_x, OString::number(std::lround(fSx)),
4519 XML_y, OString::number(std::lround(fSy)));
4520 mpFS->endElementNS(XML_a, XML_lnTo);
4521 }
4522 else
4523 {
4524 mpFS->startElementNS(XML_a, XML_moveTo);
4525 mpFS->singleElementNS(XML_a, XML_pt, XML_x, OString::number(std::lround(fSx)),
4526 XML_y, OString::number(std::lround(fSy)));
4527 mpFS->endElementNS(XML_a, XML_moveTo);
4528 }
4529 // write markup for arcTo
4531 mpFS->singleElement(
4532 FSNS(XML_a, XML_arcTo), XML_wR, OString::number(std::lround(fWR)), XML_hR,
4533 OString::number(std::lround(fHR)), XML_stAng, OString::number(nStartAng),
4534 XML_swAng, OString::number(nSwingAng));
4535
4536 getEllipsePointFromViewAngle(rfCurrentX, rfCurrentY, fWR, fHR, fCx, fCy, fEndAngle);
4537 rbCurrentValid = true;
4538 rnPairIndex += 3;
4539 break;
4540 }
4541 case ARCTO:
4542 case ARC:
4543 case CLOCKWISEARCTO:
4544 case CLOCKWISEARC:
4545 {
4546 if (rnPairIndex + 3 >= rPairs.getLength())
4547 return false;
4548
4549 // read parameters
4550 double fX1 = 0.0;
4551 rCustomShape2d.GetParameter(fX1, rPairs[rnPairIndex].First, bReplaceGeoWidth, false);
4552 double fY1 = 0.0;
4553 rCustomShape2d.GetParameter(fY1, rPairs[rnPairIndex].Second, false, bReplaceGeoHeight);
4554 double fX2 = 0.0;
4555 rCustomShape2d.GetParameter(fX2, rPairs[rnPairIndex + 1].First, bReplaceGeoWidth,
4556 false);
4557 double fY2 = 0.0;
4558 rCustomShape2d.GetParameter(fY2, rPairs[rnPairIndex + 1].Second, false,
4559 bReplaceGeoHeight);
4560 double fX3 = 0.0;
4561 rCustomShape2d.GetParameter(fX3, rPairs[rnPairIndex + 2].First, bReplaceGeoWidth,
4562 false);
4563 double fY3 = 0.0;
4564 rCustomShape2d.GetParameter(fY3, rPairs[rnPairIndex + 2].Second, false,
4565 bReplaceGeoHeight);
4566 double fX4 = 0.0;
4567 rCustomShape2d.GetParameter(fX4, rPairs[rnPairIndex + 3].First, bReplaceGeoWidth,
4568 false);
4569 double fY4 = 0.0;
4570 rCustomShape2d.GetParameter(fY4, rPairs[rnPairIndex + 3].Second, false,
4571 bReplaceGeoHeight);
4572 // calculate ellipse parameter
4573 const double fWR = (fX2 - fX1) / 2.0;
4574 const double fHR = (fY2 - fY1) / 2.0;
4575 const double fCx = (fX1 + fX2) / 2.0;
4576 const double fCy = (fY1 + fY2) / 2.0;
4577 // calculate start angle
4578 double fStartAngle = 0.0;
4579 double fPx = 0.0;
4580 double fPy = 0.0;
4581 getEllipsePointAndAngleFromRayPoint(fStartAngle, fPx, fPy, fWR, fHR, fCx, fCy, fX3,
4582 fY3);
4583 // markup for going to start point
4584 // lnTo requires a valid current point.
4585 if ((eCommand == ARCTO || eCommand == CLOCKWISEARCTO) && rbCurrentValid)
4586 {
4587 mpFS->startElementNS(XML_a, XML_lnTo);
4588 mpFS->singleElementNS(XML_a, XML_pt, XML_x, OString::number(std::lround(fPx)),
4589 XML_y, OString::number(std::lround(fPy)));
4590 mpFS->endElementNS(XML_a, XML_lnTo);
4591 }
4592 else
4593 {
4594 mpFS->startElementNS(XML_a, XML_moveTo);
4595 mpFS->singleElementNS(XML_a, XML_pt, XML_x, OString::number(std::lround(fPx)),
4596 XML_y, OString::number(std::lround(fPy)));
4597 mpFS->endElementNS(XML_a, XML_moveTo);
4598 }
4599 // calculate swing angle
4600 double fEndAngle = 0.0;
4601 getEllipsePointAndAngleFromRayPoint(fEndAngle, fPx, fPy, fWR, fHR, fCx, fCy, fX4, fY4);
4602 double fSwingAngle(fEndAngle - fStartAngle);
4603 const bool bIsClockwise(eCommand == CLOCKWISEARCTO || eCommand == CLOCKWISEARC);
4604 if (bIsClockwise && fSwingAngle < 0)
4605 fSwingAngle += 360.0;
4606 else if (!bIsClockwise && fSwingAngle > 0)
4607 fSwingAngle -= 360.0;
4608 // markup for arcTo
4609 // ToDo: write markup for case zero width or height of ellipse
4610 const sal_Int32 nStartAng(std::lround(fStartAngle * 60000));
4611 const sal_Int32 nSwingAng(std::lround(fSwingAngle * 60000));
4612 mpFS->singleElement(FSNS(XML_a, XML_arcTo), XML_wR, OString::number(std::lround(fWR)),
4613 XML_hR, OString::number(std::lround(fHR)), XML_stAng,
4614 OString::number(nStartAng), XML_swAng, OString::number(nSwingAng));
4615 rfCurrentX = fPx;
4616 rfCurrentY = fPy;
4617 rbCurrentValid = true;
4618 rnPairIndex += 4;
4619 break;
4620 }
4621 case ELLIPTICALQUADRANTX:
4622 case ELLIPTICALQUADRANTY:
4623 {
4624 if (rnPairIndex >= rPairs.getLength())
4625 return false;
4626
4627 // read parameters
4628 double fX = 0.0;
4629 rCustomShape2d.GetParameter(fX, rPairs[rnPairIndex].First, bReplaceGeoWidth, false);
4630 double fY = 0.0;
4631 rCustomShape2d.GetParameter(fY, rPairs[rnPairIndex].Second, false, bReplaceGeoHeight);
4632
4633 // Prepare parameters for arcTo
4634 if (rbCurrentValid)
4635 {
4636 double fWR = std::abs(rfCurrentX - fX);
4637 double fHR = std::abs(rfCurrentY - fY);
4638 double fStartAngle(0.0);
4639 double fSwingAngle(0.0);
4640 // The starting direction of the arc toggles between X and Y
4641 if ((eCommand == ELLIPTICALQUADRANTX && !(nCount % 2))
4642 || (eCommand == ELLIPTICALQUADRANTY && (nCount % 2)))
4643 {
4644 // arc starts horizontal
4645 fStartAngle = fY < rfCurrentY ? 90.0 : 270.0;
4646 const bool bClockwise = (fX < rfCurrentX && fY < rfCurrentY)
4647 || (fX > rfCurrentX && fY > rfCurrentY);
4648 fSwingAngle = bClockwise ? 90.0 : -90.0;
4649 }
4650 else
4651 {
4652 // arc starts vertical
4653 fStartAngle = fX < rfCurrentX ? 0.0 : 180.0;
4654 const bool bClockwise = (fX < rfCurrentX && fY > rfCurrentY)
4655 || (fX > rfCurrentX && fY < rfCurrentY);
4656 fSwingAngle = bClockwise ? 90.0 : -90.0;
4657 }
4658 sal_Int32 nStartAng(std::lround(fStartAngle * 60000));
4659 sal_Int32 nSwingAng(std::lround(fSwingAngle * 60000));
4660 mpFS->singleElement(
4661 FSNS(XML_a, XML_arcTo), XML_wR, OString::number(std::lround(fWR)), XML_hR,
4662 OString::number(std::lround(fHR)), XML_stAng, OString::number(nStartAng),
4663 XML_swAng, OString::number(nSwingAng));
4664 }
4665 else
4666 {
4667 // faulty path, but we continue with the target point
4668 mpFS->startElementNS(XML_a, XML_moveTo);
4669 WriteCustomGeometryPoint(rPairs[rnPairIndex], rCustomShape2d, bReplaceGeoWidth,
4670 bReplaceGeoHeight);
4671 mpFS->endElementNS(XML_a, XML_moveTo);
4672 }
4673 rfCurrentX = fX;
4674 rfCurrentY = fY;
4675 rbCurrentValid = true;
4676 rnPairIndex++;
4677 break;
4678 }
4679 case QUADRATICCURVETO:
4680 {
4681 if (rnPairIndex + 1 >= rPairs.getLength())
4682 return false;
4683
4684 mpFS->startElementNS(XML_a, XML_quadBezTo);
4685 for (sal_uInt8 i = 0; i < 2; ++i)
4686 {
4687 WriteCustomGeometryPoint(rPairs[rnPairIndex + i], rCustomShape2d, bReplaceGeoWidth,
4688 bReplaceGeoHeight);
4689 }
4690 mpFS->endElementNS(XML_a, XML_quadBezTo);
4691 rCustomShape2d.GetParameter(rfCurrentX, rPairs[rnPairIndex + 1].First, bReplaceGeoWidth,
4692 false);
4693 rCustomShape2d.GetParameter(rfCurrentY, rPairs[rnPairIndex + 1].Second, false,
4694 bReplaceGeoHeight);
4695 rbCurrentValid = true;
4696 rnPairIndex += 2;
4697 break;
4698 }
4699 case ARCANGLETO:
4700 {
4701 if (rnPairIndex + 1 >= rPairs.getLength())
4702 return false;
4703
4704 double fWR = 0.0;
4705 rCustomShape2d.GetParameter(fWR, rPairs[rnPairIndex].First, false, false);
4706 double fHR = 0.0;
4707 rCustomShape2d.GetParameter(fHR, rPairs[rnPairIndex].Second, false, false);
4708 double fStartAngle = 0.0;
4709 rCustomShape2d.GetParameter(fStartAngle, rPairs[rnPairIndex + 1].First, false, false);
4710 sal_Int32 nStartAng(std::lround(fStartAngle * 60000));
4711 double fSwingAng = 0.0;
4712 rCustomShape2d.GetParameter(fSwingAng, rPairs[rnPairIndex + 1].Second, false, false);
4713 sal_Int32 nSwingAng(std::lround(fSwingAng * 60000));
4714 mpFS->singleElement(FSNS(XML_a, XML_arcTo), XML_wR, OString::number(fWR), XML_hR,
4715 OString::number(fHR), XML_stAng, OString::number(nStartAng),
4716 XML_swAng, OString::number(nSwingAng));
4717 double fPx = 0.0;
4718 double fPy = 0.0;
4719 getEllipsePointFromViewAngle(fPx, fPy, fWR, fHR, 0.0, 0.0, fStartAngle);
4720 double fCx = rfCurrentX - fPx;
4721 double fCy = rfCurrentY - fPy;
4722 getEllipsePointFromViewAngle(rfCurrentX, rfCurrentY, fWR, fHR, fCx, fCy,
4723 fStartAngle + fSwingAng);
4724 rbCurrentValid = true;
4725 rnPairIndex += 2;
4726 break;
4727 }
4728 default:
4729 // do nothing
4730 break;
4731 }
4732 return true;
4733}
4736 const drawing::EnhancedCustomShapeParameterPair& rParamPair,
4737 const EnhancedCustomShape2d& rCustomShape2d, const bool bReplaceGeoWidth,
4738 const bool bReplaceGeoHeight)
4739{
4740 sal_Int32 nX
4741 = GetCustomGeometryPointValue(rParamPair.First, rCustomShape2d, bReplaceGeoWidth, false);
4742 sal_Int32 nY
4743 = GetCustomGeometryPointValue(rParamPair.Second, rCustomShape2d, false, bReplaceGeoHeight);
4744
4745 mpFS->singleElementNS(XML_a, XML_pt, XML_x, OString::number(nX), XML_y, OString::number(nY));
4746}
4749{
4750 // This method is used for export to docx in case WriteCustomGeometry fails.
4751 mpFS->startElementNS(XML_a, XML_custGeom);
4752 mpFS->singleElementNS(XML_a, XML_avLst);
4753 mpFS->singleElementNS(XML_a, XML_gdLst);
4754 mpFS->singleElementNS(XML_a, XML_ahLst);
4755 mpFS->singleElementNS(XML_a, XML_rect, XML_l, "0", XML_t, "0", XML_r, "r", XML_b, "b");
4756 mpFS->singleElementNS(XML_a, XML_pathLst);
4757 mpFS->endElementNS(XML_a, XML_custGeom);
4758}
4759
4760// version for SdrPathObj
4761void DrawingML::WritePolyPolygon(const css::uno::Reference<css::drawing::XShape>& rXShape,
4762 const bool bClosed)
4763{
4765 // In case of Writer, the parent element is <wps:spPr>, and there the
4766 // <a:custGeom> element is not optional.
4767 if (aPolyPolygon.Count() < 1 && GetDocumentType() != DOCUMENT_DOCX)
4768 return;
4769
4770 mpFS->startElementNS(XML_a, XML_custGeom);
4771 mpFS->singleElementNS(XML_a, XML_avLst);
4772 mpFS->singleElementNS(XML_a, XML_gdLst);
4773 mpFS->singleElementNS(XML_a, XML_ahLst);
4774 mpFS->singleElementNS(XML_a, XML_rect, XML_l, "0", XML_t, "0", XML_r, "r", XML_b, "b");
4775
4776 mpFS->startElementNS(XML_a, XML_pathLst);
4777
4778 awt::Size aSize = rXShape->getSize();
4779 awt::Point aPos = rXShape->getPosition();
4780 Reference<XPropertySet> xPropertySet(rXShape, UNO_QUERY);
4781 uno::Reference<XPropertySetInfo> xPropertySetInfo = xPropertySet->getPropertySetInfo();
4782 if (xPropertySetInfo->hasPropertyByName("AnchorPosition"))
4783 {
4784 awt::Point aAnchorPosition;
4785 xPropertySet->getPropertyValue("AnchorPosition") >>= aAnchorPosition;
4786 aPos.X += aAnchorPosition.X;
4787 aPos.Y += aAnchorPosition.Y;
4788 }
4789
4790 // Only closed SdrPathObj can be filled
4791 std::optional<OString> sFill;
4792 if (!bClosed)
4793 sFill = "none"; // for possible values see ST_PathFillMode in OOXML standard
4794
4795 // Put all polygons of rPolyPolygon in the same path element
4796 // to subtract the overlapped areas.
4797 mpFS->startElementNS(XML_a, XML_path, XML_fill, sFill, XML_w, OString::number(aSize.Width),
4798 XML_h, OString::number(aSize.Height));
4799
4800 for (sal_uInt16 i = 0; i < aPolyPolygon.Count(); i++)
4801 {
4802 const tools::Polygon& aPoly = aPolyPolygon[i];
4803
4804 if (aPoly.GetSize() > 0)
4805 {
4806 mpFS->startElementNS(XML_a, XML_moveTo);
4807
4808 mpFS->singleElementNS(XML_a, XML_pt, XML_x, OString::number(aPoly[0].X() - aPos.X),
4809 XML_y, OString::number(aPoly[0].Y() - aPos.Y));
4810
4811 mpFS->endElementNS(XML_a, XML_moveTo);
4812 }
4813
4814 for (sal_uInt16 j = 1; j < aPoly.GetSize(); j++)
4815 {
4816 PolyFlags flags = aPoly.GetFlags(j);
4817 if (flags == PolyFlags::Control)
4818 {
4819 // a:cubicBezTo can only contain 3 a:pt elements, so we need to make sure of this
4820 if (j + 2 < aPoly.GetSize() && aPoly.GetFlags(j + 1) == PolyFlags::Control
4821 && aPoly.GetFlags(j + 2) != PolyFlags::Control)
4822 {
4823 mpFS->startElementNS(XML_a, XML_cubicBezTo);
4824 for (sal_uInt8 k = 0; k <= 2; ++k)
4825 {
4826 mpFS->singleElementNS(XML_a, XML_pt, XML_x,
4827 OString::number(aPoly[j + k].X() - aPos.X), XML_y,
4828 OString::number(aPoly[j + k].Y() - aPos.Y));
4829 }
4830 mpFS->endElementNS(XML_a, XML_cubicBezTo);
4831 j += 2;
4832 }
4833 }
4834 else if (flags == PolyFlags::Normal)
4835 {
4836 mpFS->startElementNS(XML_a, XML_lnTo);
4837 mpFS->singleElementNS(XML_a, XML_pt, XML_x, OString::number(aPoly[j].X() - aPos.X),
4838 XML_y, OString::number(aPoly[j].Y() - aPos.Y));
4839 mpFS->endElementNS(XML_a, XML_lnTo);
4840 }
4841 }
4842 }
4843 if (bClosed)
4844 mpFS->singleElementNS(XML_a, XML_close);
4845 mpFS->endElementNS(XML_a, XML_path);
4846
4847 mpFS->endElementNS(XML_a, XML_pathLst);
4848
4849 mpFS->endElementNS(XML_a, XML_custGeom);
4850}
4852void DrawingML::WriteConnectorConnections( sal_Int32 nStartGlueId, sal_Int32 nEndGlueId, sal_Int32 nStartID, sal_Int32 nEndID )
4853{
4854 if( nStartID != -1 )
4855 {
4856 mpFS->singleElementNS( XML_a, XML_stCxn,
4857 XML_id, OString::number(nStartID),
4858 XML_idx, OString::number(nStartGlueId) );
4859 }
4860 if( nEndID != -1 )
4861 {
4862 mpFS->singleElementNS( XML_a, XML_endCxn,
4863 XML_id, OString::number(nEndID),
4864 XML_idx, OString::number(nEndGlueId) );
4865 }
4866}
4868sal_Unicode DrawingML::SubstituteBullet( sal_Unicode cBulletId, css::awt::FontDescriptor& rFontDesc )
4869{
4870 if ( IsStarSymbol(rFontDesc.Name) )
4871 {
4872 rtl_TextEncoding eCharSet = rFontDesc.CharSet;
4873 cBulletId = msfilter::util::bestFitOpenSymbolToMSFont(cBulletId, eCharSet, rFontDesc.Name);
4874 rFontDesc.CharSet = eCharSet;
4875 }
4876
4877 return cBulletId;
4878}
4881 const OUString& sFullStream,
4882 std::u16string_view sRelativeStream,
4883 const Reference< XOutputStream >& xParentRelation,
4884 const char* sContentType,
4885 const char* sRelationshipType,
4886 OUString* pRelationshipId )
4887{
4888 OUString sRelationshipId;
4889 if (xParentRelation.is())
4890 sRelationshipId = GetFB()->addRelation( xParentRelation, OUString::createFromAscii( sRelationshipType), sRelativeStream );
4891 else
4892 sRelationshipId = GetFB()->addRelation( OUString::createFromAscii( sRelationshipType ), sRelativeStream );
4893
4894 if( pRelationshipId )
4895 *pRelationshipId = sRelationshipId;
4896
4897 sax_fastparser::FSHelperPtr p = GetFB()->openFragmentStreamWithSerializer( sFullStream, OUString::createFromAscii( sContentType ) );
4898
4899 return p;
4900}
4902void DrawingML::WriteFill( const Reference< XPropertySet >& xPropSet )
4903{
4904 if ( !GetProperty( xPropSet, "FillStyle" ) )
4905 return;
4906 FillStyle aFillStyle( FillStyle_NONE );
4907 xPropSet->getPropertyValue( "FillStyle" ) >>= aFillStyle;
4908
4909 // map full transparent background to no fill
4910 if ( aFillStyle == FillStyle_SOLID && GetProperty( xPropSet, "FillTransparence" ) )
4911 {
4912 sal_Int16 nVal = 0;
4913 xPropSet->getPropertyValue( "FillTransparence" ) >>= nVal;
4914 if ( nVal == 100 )
4915 aFillStyle = FillStyle_NONE;
4916 }
4917 if (aFillStyle == FillStyle_SOLID && GetProperty( xPropSet, "FillTransparenceGradient"))
4918 {
4919 awt::Gradient aTransparenceGradient;
4920 mAny >>= aTransparenceGradient;
4921 if (aTransparenceGradient.StartColor == 0xffffff && aTransparenceGradient.EndColor == 0xffffff)
4922 aFillStyle = FillStyle_NONE;
4923 }
4924
4925 bool bUseBackground(false);
4926 if (GetProperty(xPropSet, "FillUseSlideBackground"))
4927 xPropSet->getPropertyValue("FillUseSlideBackground") >>= bUseBackground;
4928
4929 switch( aFillStyle )
4930 {
4931 case FillStyle_SOLID :
4932 WriteSolidFill( xPropSet );
4933 break;
4934 case FillStyle_GRADIENT :
4935 WriteGradientFill( xPropSet );
4936 break;
4937 case FillStyle_BITMAP :
4938 WriteBlipFill( xPropSet, "FillBitmap" );
4939 break;
4940 case FillStyle_HATCH :
4941 WritePattFill( xPropSet );
4942 break;
4943 case FillStyle_NONE:
4944 if (!bUseBackground) // attribute `useBgFill` will be written at parent p:sp shape
4945 mpFS->singleElementNS(XML_a, XML_noFill);
4946 break;
4947 default:
4948 ;
4949 }
4950}
4952void DrawingML::WriteStyleProperties( sal_Int32 nTokenId, const Sequence< PropertyValue >& aProperties )
4953{
4954 if( aProperties.hasElements() )
4955 {
4956 OUString sSchemeClr;
4957 sal_uInt32 nIdx = 0;
4958 Sequence< PropertyValue > aTransformations;
4959 for( const auto& rProp : aProperties)
4960 {
4961 if( rProp.Name == "SchemeClr" )
4962 rProp.Value >>= sSchemeClr;
4963 else if( rProp.Name == "Idx" )
4964 rProp.Value >>= nIdx;
4965 else if( rProp.Name == "Transformations" )
4966 rProp.Value >>= aTransformations;
4967 }
4968 mpFS->startElementNS(XML_a, nTokenId, XML_idx, OString::number(nIdx));
4969 WriteColor(sSchemeClr, aTransformations);
4970 mpFS->endElementNS( XML_a, nTokenId );
4971 }
4972 else
4973 {
4974 // write mock <a:*Ref> tag
4975 mpFS->singleElementNS(XML_a, nTokenId, XML_idx, OString::number(0));
4976 }
4977}
4980{
4981 // check existence of the grab bag
4982 if ( !GetProperty( xPropSet, "InteropGrabBag" ) )
4983 return;
4984
4985 // extract the relevant properties from the grab bag
4987 Sequence< PropertyValue > aFillRefProperties, aLnRefProperties, aEffectRefProperties;
4988 mAny >>= aGrabBag;
4989 for( const auto& rProp : std::as_const(aGrabBag))
4990 {
4991 if( rProp.Name == "StyleFillRef" )
4992 rProp.Value >>= aFillRefProperties;
4993 else if( rProp.Name == "StyleLnRef" )
4994 rProp.Value >>= aLnRefProperties;
4995 else if( rProp.Name == "StyleEffectRef" )
4996 rProp.Value >>= aEffectRefProperties;
4997 }
4998
4999 WriteStyleProperties( XML_lnRef, aLnRefProperties );
5000 WriteStyleProperties( XML_fillRef, aFillRefProperties );
5001 WriteStyleProperties( XML_effectRef, aEffectRefProperties );
5002
5003 // write mock <a:fontRef>
5004 mpFS->singleElementNS(XML_a, XML_fontRef, XML_idx, "minor");
5005}
5007void DrawingML::WriteShapeEffect( std::u16string_view sName, const Sequence< PropertyValue >& aEffectProps )
5008{
5009 if( !aEffectProps.hasElements() )
5010 return;
5011
5012 // assign the proper tag and enable bContainsColor if necessary
5013 sal_Int32 nEffectToken = 0;
5014 bool bContainsColor = false;
5015 if( sName == u"outerShdw" )
5016 {
5017 nEffectToken = FSNS( XML_a, XML_outerShdw );
5018 bContainsColor = true;
5019 }
5020 else if( sName == u"innerShdw" )
5021 {
5022 nEffectToken = FSNS( XML_a, XML_innerShdw );
5023 bContainsColor = true;
5024 }
5025 else if( sName == u"glow" )
5026 {
5027 nEffectToken = FSNS( XML_a, XML_glow );
5028 bContainsColor = true;
5029 }
5030 else if( sName == u"softEdge" )
5031 nEffectToken = FSNS( XML_a, XML_softEdge );
5032 else if( sName == u"reflection" )
5033 nEffectToken = FSNS( XML_a, XML_reflection );
5034 else if( sName == u"blur" )
5035 nEffectToken = FSNS( XML_a, XML_blur );
5036
5037 OUString sSchemeClr;
5038 ::Color nRgbClr;
5039 sal_Int32 nAlpha = MAX_PERCENT;
5040 Sequence< PropertyValue > aTransformations;
5041 rtl::Reference<sax_fastparser::FastAttributeList> aOuterShdwAttrList = FastSerializerHelper::createAttrList();
5042 for( const auto& rEffectProp : aEffectProps )
5043 {
5044 if( rEffectProp.Name == "Attribs" )
5045 {
5046 // read tag attributes
5047 uno::Sequence< beans::PropertyValue > aOuterShdwProps;
5048 rEffectProp.Value >>= aOuterShdwProps;
5049 for( const auto& rOuterShdwProp : std::as_const(aOuterShdwProps) )
5050 {
5051 if( rOuterShdwProp.Name == "algn" )
5052 {
5053 OUString sVal;
5054 rOuterShdwProp.Value >>= sVal;
5055 aOuterShdwAttrList->add( XML_algn, sVal );
5056 }
5057 else if( rOuterShdwProp.Name == "blurRad" )
5058 {
5059 sal_Int64 nVal = 0;
5060 rOuterShdwProp.Value >>= nVal;
5061 aOuterShdwAttrList->add( XML_blurRad, OString::number( nVal ).getStr() );
5062 }
5063 else if( rOuterShdwProp.Name == "dir" )
5064 {
5065 sal_Int32 nVal = 0;
5066 rOuterShdwProp.Value >>= nVal;
5067 aOuterShdwAttrList->add( XML_dir, OString::number( nVal ).getStr() );
5068 }
5069 else if( rOuterShdwProp.Name == "dist" )
5070 {
5071 sal_Int32 nVal = 0;
5072 rOuterShdwProp.Value >>= nVal;
5073 aOuterShdwAttrList->add( XML_dist, OString::number( nVal ).getStr() );
5074 }
5075 else if( rOuterShdwProp.Name == "kx" )
5076 {
5077 sal_Int32 nVal = 0;
5078 rOuterShdwProp.Value >>= nVal;
5079 aOuterShdwAttrList->add( XML_kx, OString::number( nVal ).getStr() );
5080 }
5081 else if( rOuterShdwProp.Name == "ky" )
5082 {
5083 sal_Int32 nVal = 0;
5084 rOuterShdwProp.Value >>= nVal;
5085 aOuterShdwAttrList->add( XML_ky, OString::number( nVal ).getStr() );
5086 }
5087 else if( rOuterShdwProp.Name == "rotWithShape" )
5088 {
5089 sal_Int32 nVal = 0;
5090 rOuterShdwProp.Value >>= nVal;
5091 aOuterShdwAttrList->add( XML_rotWithShape, OString::number( nVal ).getStr() );
5092 }
5093 else if( rOuterShdwProp.Name == "sx" )
5094 {
5095 sal_Int32 nVal = 0;
5096 rOuterShdwProp.Value >>= nVal;
5097 aOuterShdwAttrList->add( XML_sx, OString::number( nVal ).getStr() );
5098 }
5099 else if( rOuterShdwProp.Name == "sy" )
5100 {
5101 sal_Int32 nVal = 0;
5102 rOuterShdwProp.Value >>= nVal;
5103 aOuterShdwAttrList->add( XML_sy, OString::number( nVal ).getStr() );
5104 }
5105 else if( rOuterShdwProp.Name == "rad" )
5106 {
5107 sal_Int64 nVal = 0;
5108 rOuterShdwProp.Value >>= nVal;
5109 aOuterShdwAttrList->add( XML_rad, OString::number( nVal ).getStr() );
5110 }
5111 else if( rOuterShdwProp.Name == "endA" )
5112 {
5113 sal_Int32 nVal = 0;
5114 rOuterShdwProp.Value >>= nVal;
5115 aOuterShdwAttrList->add( XML_endA, OString::number( nVal ).getStr() );
5116 }
5117 else if( rOuterShdwProp.Name == "endPos" )
5118 {
5119 sal_Int32 nVal = 0;
5120 rOuterShdwProp.Value >>= nVal;
5121 aOuterShdwAttrList->add( XML_endPos, OString::number( nVal ).getStr() );
5122 }
5123 else if( rOuterShdwProp.Name == "fadeDir" )
5124 {
5125 sal_Int32 nVal = 0;
5126 rOuterShdwProp.Value >>= nVal;
5127 aOuterShdwAttrList->add( XML_fadeDir, OString::number( nVal ).getStr() );
5128 }
5129 else if( rOuterShdwProp.Name == "stA" )
5130 {
5131 sal_Int32 nVal = 0;
5132 rOuterShdwProp.Value >>= nVal;
5133 aOuterShdwAttrList->add( XML_stA, OString::number( nVal ).getStr() );
5134 }
5135 else if( rOuterShdwProp.Name == "stPos" )
5136 {
5137 sal_Int32 nVal = 0;
5138 rOuterShdwProp.Value >>= nVal;
5139 aOuterShdwAttrList->add( XML_stPos, OString::number( nVal ).getStr() );
5140 }
5141 else if( rOuterShdwProp.Name == "grow" )
5142 {
5143 sal_Int32 nVal = 0;
5144 rOuterShdwProp.Value >>= nVal;
5145 aOuterShdwAttrList->add( XML_grow, OString::number( nVal ).getStr() );
5146 }
5147 }
5148 }
5149 else if(rEffectProp.Name == "RgbClr")
5150 {
5151 rEffectProp.Value >>= nRgbClr;
5152 }
5153 else if(rEffectProp.Name == "RgbClrTransparency")
5154 {
5155 sal_Int32 nTransparency;
5156 if (rEffectProp.Value >>= nTransparency)
5157 // Calculate alpha value (see oox/source/drawingml/color.cxx : getTransparency())
5158 nAlpha = MAX_PERCENT - ( PER_PERCENT * nTransparency );
5159 }
5160 else if(rEffectProp.Name == "SchemeClr")
5161 {
5162 rEffectProp.Value >>= sSchemeClr;
5163 }
5164 else if(rEffectProp.Name == "SchemeClrTransformations")
5165 {
5166 rEffectProp.Value >>= aTransformations;
5167 }
5168 }
5169
5170 if( nEffectToken <= 0 )
5171 return;
5172
5173 mpFS->startElement( nEffectToken, aOuterShdwAttrList );
5174
5175 if( bContainsColor )
5176 {
5177 if( sSchemeClr.isEmpty() )
5178 WriteColor( nRgbClr, nAlpha );
5179 else
5180 WriteColor( sSchemeClr, aTransformations );
5181 }
5182
5183 mpFS->endElement( nEffectToken );
5184}
5186static sal_Int32 lcl_CalculateDist(const double dX, const double dY)
5187{
5188 return static_cast< sal_Int32 >(sqrt(dX*dX + dY*dY) * 360);
5189}
5191static sal_Int32 lcl_CalculateDir(const double dX, const double dY)
5192{
5193 return (static_cast< sal_Int32 >(basegfx::rad2deg<60000>(atan2(dY,dX))) + 21600000) % 21600000;
5194}
5197{
5198 Sequence< PropertyValue > aGrabBag, aEffects, aOuterShdwProps;
5199 bool bHasInteropGrabBag = rXPropSet->getPropertySetInfo()->hasPropertyByName("InteropGrabBag");
5200 if (bHasInteropGrabBag && GetProperty(rXPropSet, "InteropGrabBag"))
5201 {
5202 mAny >>= aGrabBag;
5203 auto pProp = std::find_if(std::cbegin(aGrabBag), std::cend(aGrabBag),
5204 [](const PropertyValue& rProp) { return rProp.Name == "EffectProperties"; });
5205 if (pProp != std::cend(aGrabBag))
5206 {
5207 pProp->Value >>= aEffects;
5208 auto pEffect = std::find_if(std::cbegin(aEffects), std::cend(aEffects),
5209 [](const PropertyValue& rEffect) { return rEffect.Name == "outerShdw"; });
5210 if (pEffect != std::cend(aEffects))
5211 pEffect->Value >>= aOuterShdwProps;
5212 }
5213 }
5214
5215 // tdf#132201: the order of effects is important. Effects order (CT_EffectList in ECMA-376):
5216 // blur -> fillOverlay -> glow -> innerShdw -> outerShdw -> prstShdw -> reflection -> softEdge
5217
5218 if( !aEffects.hasElements() )
5219 {
5220 bool bHasShadow = false;
5221 if( GetProperty( rXPropSet, "Shadow" ) )
5222 mAny >>= bHasShadow;
5223 bool bHasEffects = bHasShadow;
5224 if (!bHasEffects && GetProperty(rXPropSet, "GlowEffectRadius"))
5225 {
5226 sal_Int32 rad = 0;
5227 mAny >>= rad;
5228 bHasEffects = rad > 0;
5229 }
5230 if (!bHasEffects && GetProperty(rXPropSet, "SoftEdgeRadius"))
5231 {
5232 sal_Int32 rad = 0;
5233 mAny >>= rad;
5234 bHasEffects = rad > 0;
5235 }
5236
5237 if (bHasEffects)
5238 {
5239 mpFS->startElementNS(XML_a, XML_effectLst);
5240 WriteGlowEffect(rXPropSet);
5241 if( bHasShadow )
5242 {
5243 double dX = +0.0, dY = +0.0;
5244 sal_Int32 nBlur =0;
5245 rXPropSet->getPropertyValue( "ShadowXDistance" ) >>= dX;
5246 rXPropSet->getPropertyValue( "ShadowYDistance" ) >>= dY;
5247 rXPropSet->getPropertyValue( "ShadowBlur" ) >>= nBlur;
5248
5249 Sequence< PropertyValue > aShadowAttribsGrabBag{
5253 comphelper::makePropertyValue("rotWithShape", false) //ooxml default is 'true', so must write it
5254 };
5255
5256 Sequence< PropertyValue > aShadowGrabBag{
5257 comphelper::makePropertyValue("Attribs", aShadowAttribsGrabBag),
5258 comphelper::makePropertyValue("RgbClr", rXPropSet->getPropertyValue( "ShadowColor" )),
5259 comphelper::makePropertyValue("RgbClrTransparency", rXPropSet->getPropertyValue( "ShadowTransparence" ))
5260 };
5261
5262 WriteShapeEffect( u"outerShdw", aShadowGrabBag );
5263 }
5264 WriteSoftEdgeEffect(rXPropSet);
5265 mpFS->endElementNS(XML_a, XML_effectLst);
5266 }
5267 }
5268 else
5269 {
5270 for( auto& rOuterShdwProp : asNonConstRange(aOuterShdwProps) )
5271 {
5272 if( rOuterShdwProp.Name == "Attribs" )
5273 {
5274 Sequence< PropertyValue > aAttribsProps;
5275 rOuterShdwProp.Value >>= aAttribsProps;
5276
5277 double dX = +0.0, dY = +0.0;
5278 sal_Int32 nBlur =0;
5279 rXPropSet->getPropertyValue( "ShadowXDistance" ) >>= dX;
5280 rXPropSet->getPropertyValue( "ShadowYDistance" ) >>= dY;
5281 rXPropSet->getPropertyValue( "ShadowBlur" ) >>= nBlur;
5282
5283
5284 for( auto& rAttribsProp : asNonConstRange(aAttribsProps) )
5285 {
5286 if( rAttribsProp.Name == "dist" )
5287 {
5288 rAttribsProp.Value <<= lcl_CalculateDist(dX, dY);
5289 }
5290 else if( rAttribsProp.Name == "dir" )
5291 {
5292 rAttribsProp.Value <<= lcl_CalculateDir(dX, dY);
5293 }
5294 else if( rAttribsProp.Name == "blurRad" )
5295 {
5296 rAttribsProp.Value <<= oox::drawingml::convertHmmToEmu(nBlur);
5297 }
5298 }
5299
5300 rOuterShdwProp.Value <<= aAttribsProps;
5301 }
5302 else if( rOuterShdwProp.Name == "RgbClr" )
5303 {
5304 rOuterShdwProp.Value = rXPropSet->getPropertyValue( "ShadowColor" );
5305 }
5306 else if( rOuterShdwProp.Name == "RgbClrTransparency" )
5307 {
5308 rOuterShdwProp.Value = rXPropSet->getPropertyValue( "ShadowTransparence" );
5309 }
5310 }
5311
5312 mpFS->startElementNS(XML_a, XML_effectLst);
5313 bool bGlowWritten = false;
5314 for( const auto& rEffect : std::as_const(aEffects) )
5315 {
5316 if (!bGlowWritten
5317 && (rEffect.Name == "innerShdw" || rEffect.Name == "outerShdw"
5318 || rEffect.Name == "prstShdw" || rEffect.Name == "reflection"
5319 || rEffect.Name == "softEdge"))
5320 {
5321 WriteGlowEffect(rXPropSet);
5322 bGlowWritten = true;
5323 }
5324
5325 if( rEffect.Name == "outerShdw" )
5326 {
5327 WriteShapeEffect( rEffect.Name, aOuterShdwProps );
5328 }
5329 else
5330 {
5331 Sequence< PropertyValue > aEffectProps;
5332 rEffect.Value >>= aEffectProps;
5333 WriteShapeEffect( rEffect.Name, aEffectProps );
5334 }
5335 }
5336 if (!bGlowWritten)
5337 WriteGlowEffect(rXPropSet);
5338 WriteSoftEdgeEffect(rXPropSet); // the last
5339
5340 mpFS->endElementNS(XML_a, XML_effectLst);
5341 }
5342}
5345{
5346 if (!rXPropSet->getPropertySetInfo()->hasPropertyByName("GlowEffectRadius"))
5347 {
5348 return;
5349 }
5350
5351 sal_Int32 nRad = 0;
5352 rXPropSet->getPropertyValue("GlowEffectRadius") >>= nRad;
5353 if (!nRad)
5354 return;
5355
5357 "rad", oox::drawingml::convertHmmToEmu(nRad)) };
5358 Sequence< PropertyValue > aGlowProps{
5359 comphelper::makePropertyValue("Attribs", aGlowAttribs),
5360 comphelper::makePropertyValue("RgbClr", rXPropSet->getPropertyValue("GlowEffectColor")),
5361 comphelper::makePropertyValue("RgbClrTransparency", rXPropSet->getPropertyValue("GlowEffectTransparency"))
5362 };
5363 // TODO other stuff like saturation or luminance
5364
5365 WriteShapeEffect(u"glow", aGlowProps);
5366}
5368void DrawingML::WriteSoftEdgeEffect(const css::uno::Reference<css::beans::XPropertySet>& rXPropSet)
5369{
5370 if (!rXPropSet->getPropertySetInfo()->hasPropertyByName("SoftEdgeRadius"))
5371 {
5372 return;
5373 }
5374
5375 sal_Int32 nRad = 0;
5376 rXPropSet->getPropertyValue("SoftEdgeRadius") >>= nRad;
5377 if (!nRad)
5378 return;
5379
5380 css::uno::Sequence<css::beans::PropertyValue> aAttribs{ comphelper::makePropertyValue(
5381 "rad", oox::drawingml::convertHmmToEmu(nRad)) };
5382 css::uno::Sequence<css::beans::PropertyValue> aProps{ comphelper::makePropertyValue("Attribs",
5383 aAttribs) };
5384
5385 WriteShapeEffect(u"softEdge", aProps);
5386}
5388void DrawingML::Write3DEffects( const Reference< XPropertySet >& xPropSet, bool bIsText )
5389{
5390 // check existence of the grab bag
5391 if( !GetProperty( xPropSet, "InteropGrabBag" ) )
5392 return;
5393
5394 // extract the relevant properties from the grab bag
5395 Sequence< PropertyValue > aGrabBag, aEffectProps, aLightRigProps, aShape3DProps;
5396 mAny >>= aGrabBag;
5397
5398 auto pShapeProp = std::find_if( std::cbegin(aGrabBag), std::cend(aGrabBag),
5399 [bIsText](const PropertyValue& rProp)
5400 { return rProp.Name == (bIsText ? u"Text3DEffectProperties" : u"3DEffectProperties"); });
5401 if (pShapeProp != std::cend(aGrabBag))
5402 {
5403 Sequence< PropertyValue > a3DEffectProps;
5404 pShapeProp->Value >>= a3DEffectProps;
5405 for( const auto& r3DEffectProp : std::as_const(a3DEffectProps) )
5406 {
5407 if( r3DEffectProp.Name == "Camera" )
5408 r3DEffectProp.Value >>= aEffectProps;
5409 else if( r3DEffectProp.Name == "LightRig" )
5410 r3DEffectProp.Value >>= aLightRigProps;
5411 else if( r3DEffectProp.Name == "Shape3D" )
5412 r3DEffectProp.Value >>= aShape3DProps;
5413 }
5414 }
5415
5416 if( !aEffectProps.hasElements() && !aLightRigProps.hasElements() && !aShape3DProps.hasElements() )
5417 return;
5418
5419 bool bCameraRotationPresent = false;
5420 rtl::Reference<sax_fastparser::FastAttributeList> aCameraAttrList = FastSerializerHelper::createAttrList();
5421 rtl::Reference<sax_fastparser::FastAttributeList> aCameraRotationAttrList = FastSerializerHelper::createAttrList();
5422 for( const auto& rEffectProp : std::as_const(aEffectProps) )
5423 {
5424 if( rEffectProp.Name == "prst" )
5425 {
5426 OUString sVal;
5427 rEffectProp.Value >>= sVal;
5428 aCameraAttrList->add(XML_prst, sVal);
5429 }
5430 else if( rEffectProp.Name == "fov" )
5431 {
5432 float fVal = 0;
5433 rEffectProp.Value >>= fVal;
5434 aCameraAttrList->add( XML_fov, OString::number( fVal * 60000 ).getStr() );
5435 }
5436 else if( rEffectProp.Name == "zoom" )
5437 {
5438 float fVal = 1;
5439 rEffectProp.Value >>= fVal;
5440 aCameraAttrList->add( XML_zoom, OString::number( fVal * 100000 ).getStr() );
5441 }
5442 else if( rEffectProp.Name == "rotLat" ||
5443 rEffectProp.Name == "rotLon" ||
5444 rEffectProp.Name == "rotRev" )
5445 {
5446 sal_Int32 nVal = 0, nToken = XML_none;
5447 rEffectProp.Value >>= nVal;
5448 if( rEffectProp.Name == "rotLat" )
5449 nToken = XML_lat;
5450 else if( rEffectProp.Name == "rotLon" )
5451 nToken = XML_lon;
5452 else if( rEffectProp.Name == "rotRev" )
5453 nToken = XML_rev;
5454 aCameraRotationAttrList->add( nToken, OString::number( nVal ).getStr() );
5455 bCameraRotationPresent = true;
5456 }
5457 }
5458
5459 bool bLightRigRotationPresent = false;
5460 rtl::Reference<sax_fastparser::FastAttributeList> aLightRigAttrList = FastSerializerHelper::createAttrList();
5461 rtl::Reference<sax_fastparser::FastAttributeList> aLightRigRotationAttrList = FastSerializerHelper::createAttrList();
5462 for( const auto& rLightRigProp : std::as_const(aLightRigProps) )
5463 {
5464 if( rLightRigProp.Name == "rig" || rLightRigProp.Name == "dir" )
5465 {
5466 OUString sVal;
5467 sal_Int32 nToken = XML_none;
5468 rLightRigProp.Value >>= sVal;
5469 if( rLightRigProp.Name == "rig" )
5470 nToken = XML_rig;
5471 else if( rLightRigProp.Name == "dir" )
5472 nToken = XML_dir;
5473 aLightRigAttrList->add(nToken, sVal);
5474 }
5475 else if( rLightRigProp.Name == "rotLat" ||
5476 rLightRigProp.Name == "rotLon" ||
5477 rLightRigProp.Name == "rotRev" )
5478 {
5479 sal_Int32 nVal = 0, nToken = XML_none;
5480 rLightRigProp.Value >>= nVal;
5481 if( rLightRigProp.Name == "rotLat" )
5482 nToken = XML_lat;
5483 else if( rLightRigProp.Name == "rotLon" )
5484 nToken = XML_lon;
5485 else if( rLightRigProp.Name == "rotRev" )
5486 nToken = XML_rev;
5487 aLightRigRotationAttrList->add( nToken, OString::number( nVal ).getStr() );
5488 bLightRigRotationPresent = true;
5489 }
5490 }
5491
5492 mpFS->startElementNS(XML_a, XML_scene3d);
5493
5494 if( aEffectProps.hasElements() )
5495 {
5496 mpFS->startElementNS( XML_a, XML_camera, aCameraAttrList );
5497 if( bCameraRotationPresent )
5498 {
5499 mpFS->singleElementNS( XML_a, XML_rot, aCameraRotationAttrList );
5500 }
5501 mpFS->endElementNS( XML_a, XML_camera );
5502 }
5503 else
5504 {
5505 // a:camera with Word default values - Word won't open the document if this is not present
5506 mpFS->singleElementNS(XML_a, XML_camera, XML_prst, "orthographicFront");
5507 }
5508
5509 if( aEffectProps.hasElements() )
5510 {
5511 mpFS->startElementNS( XML_a, XML_lightRig, aLightRigAttrList );
5512 if( bLightRigRotationPresent )
5513 {
5514 mpFS->singleElementNS( XML_a, XML_rot, aLightRigRotationAttrList );
5515 }
5516 mpFS->endElementNS( XML_a, XML_lightRig );
5517 }
5518 else
5519 {
5520 // a:lightRig with Word default values - Word won't open the document if this is not present
5521 mpFS->singleElementNS(XML_a, XML_lightRig, XML_rig, "threePt", XML_dir, "t");
5522 }
5523
5524 mpFS->endElementNS( XML_a, XML_scene3d );
5525
5526 if( !aShape3DProps.hasElements() )
5527 return;
5528
5529 bool bBevelTPresent = false, bBevelBPresent = false;
5530 Sequence< PropertyValue > aExtrusionColorProps, aContourColorProps;
5531 rtl::Reference<sax_fastparser::FastAttributeList> aBevelTAttrList = FastSerializerHelper::createAttrList();
5532 rtl::Reference<sax_fastparser::FastAttributeList> aBevelBAttrList = FastSerializerHelper::createAttrList();
5533 rtl::Reference<sax_fastparser::FastAttributeList> aShape3DAttrList = FastSerializerHelpe