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