Coverage Report

Created: 2026-11-06 11:29

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