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