Quellcodebibliothek Statistik Leitseite products/Sources/formale Sprachen/C/LibreOffice/oox/source/export/   (Office von Apache Version 25.8.3.2©)  Datei vom 5.10.2025 mit Größe 265 kB image not shown  

Quelle  drawingml.cxx   Sprache: C

 
/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/*
 * This file is part of the LibreOffice project.
 *
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
 *
 * This file incorporates work covered by the following license notice:
 *
 *   Licensed to the Apache Software Foundation (ASF) under one or more
 *   contributor license agreements. See the NOTICE file distributed
 *   with this work for additional information regarding copyright
 *   ownership. The ASF licenses this file to you under the Apache
 *   License, Version 2.0 (the "License"); you may not use this file
 *   except in compliance with the License. You may obtain a copy of
 *   the License at http://www.apache.org/licenses/LICENSE-2.0 .
 */


#include <config_features.h>

#include <config_folders.h>
#include <rtl/bootstrap.hxx>
#include <sal/log.hxx>
#include <oox/core/xmlfilterbase.hxx>
#include <oox/export/drawingml.hxx>
#include <oox/export/utils.hxx>
#include <oox/helper/propertyset.hxx>
#include <oox/drawingml/color.hxx>
#include <drawingml/fillproperties.hxx>
#include <drawingml/fontworkhelpers.hxx>
#include <drawingml/textparagraph.hxx>
#include <oox/token/namespaces.hxx>
#include <oox/token/properties.hxx>
#include <oox/token/relationship.hxx>
#include <oox/token/tokens.hxx>
#include <oox/drawingml/drawingmltypes.hxx>
#include <svtools/unitconv.hxx>
#include <sax/fastattribs.hxx>
#include <comphelper/diagnose_ex.hxx>
#include <comphelper/processfactory.hxx>
#include <i18nlangtag/languagetag.hxx>
#include <basegfx/matrix/b2dhommatrixtools.hxx>
#include <basegfx/range/b2drange.hxx>
#include <basegfx/utils/gradienttools.hxx>

#include <numeric>
#include <string_view>

#include <com/sun/star/awt/CharSet.hpp>
#include <com/sun/star/awt/FontDescriptor.hpp>
#include <com/sun/star/awt/FontSlant.hpp>
#include <com/sun/star/awt/FontStrikeout.hpp>
#include <com/sun/star/awt/FontWeight.hpp>
#include <com/sun/star/awt/FontUnderline.hpp>
#include <com/sun/star/awt/Gradient.hpp>
#include <com/sun/star/awt/Gradient2.hpp>
#include <com/sun/star/beans/XPropertySet.hpp>
#include <com/sun/star/beans/XPropertyState.hpp>
#include <com/sun/star/beans/XPropertySetInfo.hpp>
#include <com/sun/star/container/XEnumerationAccess.hpp>
#include <com/sun/star/container/XIndexAccess.hpp>
#include <com/sun/star/container/XNameAccess.hpp>
#include <com/sun/star/drawing/BitmapMode.hpp>
#include <com/sun/star/drawing/ColorMode.hpp>
#include <com/sun/star/drawing/EnhancedCustomShapeAdjustmentValue.hpp>
#include <com/sun/star/drawing/EnhancedCustomShapeParameterPair.hpp>
#include <com/sun/star/drawing/EnhancedCustomShapeParameterType.hpp>
#include <com/sun/star/drawing/EnhancedCustomShapeSegment.hpp>
#include <com/sun/star/drawing/EnhancedCustomShapeSegmentCommand.hpp>
#include <com/sun/star/drawing/FillStyle.hpp>
#include <com/sun/star/drawing/Hatch.hpp>
#include <com/sun/star/drawing/LineDash.hpp>
#include <com/sun/star/drawing/LineJoint.hpp>
#include <com/sun/star/drawing/LineStyle.hpp>
#include <com/sun/star/drawing/TextFitToSizeType.hpp>
#include <com/sun/star/drawing/TextHorizontalAdjust.hpp>
#include <com/sun/star/drawing/TextVerticalAdjust.hpp>
#include <com/sun/star/drawing/XShape.hpp>
#include <com/sun/star/drawing/XShapes.hpp>
#include <com/sun/star/frame/XModel.hpp>
#include <com/sun/star/graphic/XGraphic.hpp>
#include <com/sun/star/i18n/ScriptType.hpp>
#include <com/sun/star/i18n/BreakIterator.hpp>
#include <com/sun/star/i18n/XBreakIterator.hpp>
#include <com/sun/star/io/XOutputStream.hpp>
#include <com/sun/star/lang/XMultiServiceFactory.hpp>
#include <com/sun/star/style/LineSpacing.hpp>
#include <com/sun/star/style/LineSpacingMode.hpp>
#include <com/sun/star/text/WritingMode.hpp>
#include <com/sun/star/text/WritingMode2.hpp>
#include <com/sun/star/text/GraphicCrop.hpp>
#include <com/sun/star/text/XText.hpp>
#include <com/sun/star/text/XTextColumns.hpp>
#include <com/sun/star/text/XTextContent.hpp>
#include <com/sun/star/text/XTextField.hpp>
#include <com/sun/star/text/XTextRange.hpp>
#include <com/sun/star/text/XTextFrame.hpp>
#include <com/sun/star/style/CaseMap.hpp>
#include <com/sun/star/xml/dom/XNodeList.hpp>
#include <com/sun/star/xml/sax/Writer.hpp>
#include <com/sun/star/xml/sax/XSAXSerializable.hpp>
#include <com/sun/star/container/XNamed.hpp>
#include <com/sun/star/drawing/XDrawPages.hpp>
#include <com/sun/star/drawing/XDrawPagesSupplier.hpp>
#include <com/sun/star/drawing/RectanglePoint.hpp>

#include <comphelper/propertyvalue.hxx>
#include <comphelper/random.hxx>
#include <comphelper/seqstream.hxx>
#include <comphelper/storagehelper.hxx>
#include <comphelper/xmltools.hxx>
#include <o3tl/any.hxx>
#include <o3tl/safeint.hxx>
#include <o3tl/string_view.hxx>
#include <tools/stream.hxx>
#include <tools/UnitConversion.hxx>
#include <unotools/fontdefs.hxx>
#include <vcl/cvtgrf.hxx>
#include <vcl/svapp.hxx>
#include <vcl/embeddedfontshelper.hxx>
#include <rtl/strbuf.hxx>
#include <filter/msfilter/escherex.hxx>
#include <filter/msfilter/util.hxx>
#include <editeng/outlobj.hxx>
#include <editeng/svxenum.hxx>
#include <editeng/unonames.hxx>
#include <editeng/unoprnms.hxx>
#include <editeng/flditem.hxx>
#include <editeng/escapementitem.hxx>
#include <editeng/unonrule.hxx>
#include <docmodel/uno/UnoComplexColor.hxx>
#include <svx/svdoashp.hxx>
#include <svx/svdomedia.hxx>
#include <svx/svdtrans.hxx>
#include <svx/unoshape.hxx>
#include <svx/EnhancedCustomShape2d.hxx>
#include <drawingml/presetgeometrynames.hxx>
#include <docmodel/uno/UnoGradientTools.hxx>

using namespace ::css;
using namespace ::css::beans;
using namespace ::css::drawing;
using namespace ::css::i18n;
using namespace ::css::style;
using namespace ::css::text;
using namespace ::css::uno;
using namespace ::css::container;
using namespace ::com::sun::star::drawing::EnhancedCustomShapeSegmentCommand;

using ::css::io::XOutputStream;
using ::sax_fastparser::FSHelperPtr;
using ::sax_fastparser::FastSerializerHelper;

namespace
{
const char* g_aPredefinedClrNames[] = {
    "dk1",
    "lt1",
    "dk2",
    "lt2",
    "accent1",
    "accent2",
    "accent3",
    "accent4",
    "accent5",
    "accent6",
    "hlink",
    "folHlink",
};

/** converts 1/100mm to the ST_TextSpacingPoint (1/100pt) */
sal_Int64 toTextSpacingPoint(sal_Int64 mm100)
{
    constexpr auto mdToPt = o3tl::getConversionMulDiv(o3tl::Length::mm100, o3tl::Length::pt);
    constexpr o3tl::detail::m_and_d md(mdToPt.first * 100, mdToPt.second);
    return o3tl::convert(mm100, md.m, md.d);
}
}

namespace oox::drawingml {

URLTransformer::~URLTransformer()
{
}

OUString URLTransformer::getTransformedString(const OUString& rString) const
{
    return rString;
}

bool URLTransformer::isExternalURL(const OUString& rURL) const
{
    bool bExternal = true;
    if (rURL.startsWith("#"))
        bExternal = false;
    return bExternal;
}

GraphicExportCache& GraphicExportCache::get()
{
    static GraphicExportCache staticGraphicExportCache;
    return staticGraphicExportCache;
}

static css::uno::Any getLineDash( const css::uno::Reference<css::frame::XModel>& xModel, const OUString& rDashName )
    {
        css::uno::Reference<css::lang::XMultiServiceFactory> xFact(xModel, css::uno::UNO_QUERY);
        css::uno::Reference<css::container::XNameAccess> xNameAccess(
            xFact->createInstance(u"com.sun.star.drawing.DashTable"_ustr),
            css::uno::UNO_QUERY );
        if(xNameAccess.is())
        {
            if (!xNameAccess->hasByName(rDashName))
                return css::uno::Any();

            return xNameAccess->getByName(rDashName);
        }

        return css::uno::Any();
    }

namespace
{
void WriteGradientPath(const basegfx::BGradient& rBGradient, const FSHelperPtr&&nbsp;pFS, const bool bCircle)
{
    pFS->startElementNS(XML_a, XML_path, XML_path, bCircle ? "circle" : "rect");

    // Write the focus rectangle. Work with the focus point, and assume
    // that it extends 50% in all directions.  The below
    // left/top/right/bottom values are percentages, where 0 means the
    // edge of the tile rectangle and 100% means the center of it.
    rtl::Reference<sax_fastparser::FastAttributeList> pAttributeList(
        sax_fastparser::FastSerializerHelper::createAttrList());
    sal_Int32 nLeftPercent = rBGradient.GetXOffset();
    pAttributeList->add(XML_l, OString::number(nLeftPercent * PER_PERCENT));
    sal_Int32 nTopPercent = rBGradient.GetYOffset();
    pAttributeList->add(XML_t, OString::number(nTopPercent * PER_PERCENT));
    sal_Int32 nRightPercent = 100 - rBGradient.GetXOffset();
    pAttributeList->add(XML_r, OString::number(nRightPercent * PER_PERCENT));
    sal_Int32 nBottomPercent = 100 - rBGradient.GetYOffset();
    pAttributeList->add(XML_b, OString::number(nBottomPercent * PER_PERCENT));
    pFS->singleElementNS(XML_a, XML_fillToRect, pAttributeList);

    pFS->endElementNS(XML_a, XML_path);
}
}

// not thread safe
sal_Int32 DrawingML::mnDrawingMLCount = 0;
sal_Int32 DrawingML::mnVmlCount = 0;
sal_Int32 DrawingML::mnChartCount = 0;

DrawingML::DrawingML(::sax_fastparser::FSHelperPtr pFS, ::oox::core::XmlFilterBase* pFB, DocumentType eDocumentType, DMLTextExport* pTextExport)
    : meDocumentType(eDocumentType)
    , mpTextExport(pTextExport)
    , mpFS(std::move(pFS))
    , mpFB(pFB)
    , mbIsBackgroundDark(false)
    , mbPlaceholder(false)
{
    uno::Reference<beans::XPropertySet> xSettings(pFB->getModelFactory()->createInstance(u"com.sun.star.document.Settings"_ustr), uno::UNO_QUERY);
    if (xSettings.is())
    {
        try
        {
            xSettings->getPropertyValue(u"EmbedFonts"_ustr) >>= mbEmbedFonts;
        }
        catch (Exception& )
        {
        }
    }
}

sal_Int16 DrawingML::GetScriptType(const OUString& rStr)
{
    if (rStr.getLength() > 0)
    {
        static Reference<css::i18n::XBreakIterator> xBreakIterator =
            css::i18n::BreakIterator::create(comphelper::getProcessComponentContext());

        sal_Int16 nScriptType = xBreakIterator->getScriptType(rStr, 0);

        if (nScriptType == css::i18n::ScriptType::WEAK)
        {
            sal_Int32 nPos = xBreakIterator->nextScript(rStr, 0, nScriptType);
            if (nPos < rStr.getLength())
                nScriptType = xBreakIterator->getScriptType(rStr, nPos);

        }

        if (nScriptType != css::i18n::ScriptType::WEAK)
            return nScriptType;
    }

    return css::i18n::ScriptType::LATIN;
}

void DrawingML::ResetMlCounters()
{
    mnDrawingMLCount = 0;
    mnVmlCount = 0;
    mnChartCount = 0;
}

bool DrawingML::GetProperty( const Reference< XPropertySet >& rXPropertySet, const OUString& aName )
{
    try
    {
        mAny = rXPropertySet->getPropertyValue(aName);
        if (mAny.hasValue())
            return true;
    }
    catchconst Exception& )
    {
        /* printf ("exception when trying to get value of property: %s\n", aName.toUtf8()); */
    }
    return false;
}

bool DrawingML::GetPropertyAndState( const Reference< XPropertySet >& rXPropertySet, const Reference< XPropertyState >& rXPropertyState, const OUString& aName, PropertyState& eState )
{
    try
    {
        mAny = rXPropertySet->getPropertyValue(aName);
        if (mAny.hasValue())
        {
            eState = rXPropertyState->getPropertyState(aName);
            return true;
        }
    }
    catchconst Exception& )
    {
        /* printf ("exception when trying to get value of property: %s\n", aName.toUtf8()); */
    }
    return false;
}

namespace
{
/// Gets hexa value of color on string format.
OString getColorStr(const ::Color nColor)
{
    // Transparency is a separate element.
    OString sColor = OString::number(sal_uInt32(nColor) & 0x00FFFFFF, 16);
    if (sColor.getLength() < 6)
    {
        OStringBuffer sBuf("0");
        int remains = 5 - sColor.getLength();

        while (remains > 0)
        {
            sBuf.append("0");
            remains--;
        }

        sBuf.append(sColor);

        sColor = sBuf.toString();
    }
    return sColor;
}
}

void DrawingML::WriteColor( ::Color nColor, sal_Int32 nAlpha )
{
    const auto sColor = getColorStr(nColor);
    if( nAlpha < MAX_PERCENT )
    {
        mpFS->startElementNS(XML_a, XML_srgbClr, XML_val, sColor);
        mpFS->singleElementNS(XML_a, XML_alpha, XML_val, OString::number(nAlpha));
        mpFS->endElementNS( XML_a, XML_srgbClr );

    }
    else
    {
        mpFS->singleElementNS(XML_a, XML_srgbClr, XML_val, sColor);
    }
}

void DrawingML::WriteColor( const OUString& sColorSchemeName, const Sequence< PropertyValue >& aTransformations, sal_Int32 nAlpha )
{
    // prevent writing a tag with empty val attribute
    if( sColorSchemeName.isEmpty() )
        return;

    if( aTransformations.hasElements() )
    {
        mpFS->startElementNS(XML_a, XML_schemeClr, XML_val, sColorSchemeName);
        WriteColorTransformations( aTransformations, nAlpha );
        mpFS->endElementNS( XML_a, XML_schemeClr );
    }
    else if(nAlpha < MAX_PERCENT)
    {
        mpFS->startElementNS(XML_a, XML_schemeClr, XML_val, sColorSchemeName);
        mpFS->singleElementNS(XML_a, XML_alpha, XML_val, OString::number(nAlpha));
        mpFS->endElementNS( XML_a, XML_schemeClr );
    }
    else
    {
        mpFS->singleElementNS(XML_a, XML_schemeClr, XML_val, sColorSchemeName);
    }
}

void DrawingML::WriteColor( const ::Color nColor, const Sequence< PropertyValue >& aTransformations, sal_Int32 nAlpha )
{
    const auto sColor = getColorStr(nColor);
    if( aTransformations.hasElements() )
    {
        mpFS->startElementNS(XML_a, XML_srgbClr, XML_val, sColor);
        WriteColorTransformations(aTransformations, nAlpha);
        mpFS->endElementNS(XML_a, XML_srgbClr);
    }
    else if(nAlpha < MAX_PERCENT)
    {
        mpFS->startElementNS(XML_a, XML_srgbClr, XML_val, sColor);
        mpFS->singleElementNS(XML_a, XML_alpha, XML_val, OString::number(nAlpha));
        mpFS->endElementNS(XML_a, XML_srgbClr);
    }
    else
    {
        mpFS->singleElementNS(XML_a, XML_srgbClr, XML_val, sColor);
    }
}

void DrawingML::WriteColorTransformations( const Sequence< PropertyValue >& aTransformations, sal_Int32 nAlpha )
{
    forconst auto& rTransformation : aTransformations )
    {
        sal_Int32 nToken = Color::getColorTransformationToken( rTransformation.Name );
        if( nToken != XML_TOKEN_INVALID && rTransformation.Value.hasValue() )
        {
            if(nToken == XML_alpha && nAlpha < MAX_PERCENT)
            {
                mpFS->singleElementNS(XML_a, nToken, XML_val, OString::number(nAlpha));
            }
            else
            {
                sal_Int32 nValue = rTransformation.Value.get<sal_Int32>();
                mpFS->singleElementNS(XML_a, nToken, XML_val, OString::number(nValue));
            }
        }
    }
}

void DrawingML::WriteSolidFill( ::Color nColor, sal_Int32 nAlpha )
{
    mpFS->startElementNS(XML_a, XML_solidFill);
    WriteColor( nColor, nAlpha );
    mpFS->endElementNS( XML_a, XML_solidFill );
}

void DrawingML::WriteSolidFill( const OUString& sSchemeName, const Sequence< PropertyValue >& aTransformations, sal_Int32 nAlpha )
{
    mpFS->startElementNS(XML_a, XML_solidFill);
    WriteColor( sSchemeName, aTransformations, nAlpha );
    mpFS->endElementNS( XML_a, XML_solidFill );
}

void DrawingML::WriteSolidFill( const ::Color nColor, const Sequence< PropertyValue >& aTransformations, sal_Int32 nAlpha )
{
    mpFS->startElementNS(XML_a, XML_solidFill);
    WriteColor(nColor, aTransformations, nAlpha);
    mpFS->endElementNS(XML_a, XML_solidFill);
}

void DrawingML::WriteSolidFill( const Reference< XPropertySet >& rXPropSet )
{
    // get fill color
    if ( !GetProperty( rXPropSet, u"FillColor"_ustr ) )
        return;
    sal_uInt32 nFillColor = mAny.get<sal_uInt32>();

    // get InteropGrabBag and search the relevant attributes
    OUString sColorFillScheme;
    sal_uInt32 nOriginalColor = 0;
    Sequence< PropertyValue > aStyleProperties, aTransformations;
    if ( GetProperty( rXPropSet, u"InteropGrabBag"_ustr ) )
    {
        Sequence< PropertyValue > aGrabBag;
        mAny >>= aGrabBag;
        for (const auto& rProp : aGrabBag)
        {
            if( rProp.Name == "SpPrSolidFillSchemeClr" )
                rProp.Value >>= sColorFillScheme;
            else if( rProp.Name == "OriginalSolidFillClr" )
                rProp.Value >>= nOriginalColor;
            else if( rProp.Name == "StyleFillRef" )
                rProp.Value >>= aStyleProperties;
            else if( rProp.Name == "SpPrSolidFillSchemeClrTransformations" )
                rProp.Value >>= aTransformations;
        }
    }

    sal_Int32 nAlpha = MAX_PERCENT;
    if( GetProperty( rXPropSet, u"FillTransparence"_ustr ) )
    {
        sal_Int32 nTransparency = 0;
        mAny >>= nTransparency;
        // Calculate alpha value (see oox/source/drawingml/color.cxx : getTransparency())
        nAlpha = (MAX_PERCENT - ( PER_PERCENT * nTransparency ) );
    }

    // OOXML has no separate transparence gradient but uses transparency in the gradient stops.
    // So we merge transparency and color and use gradient fill in such case.
    basegfx::BGradient aTransparenceGradient;
    OUString sFillTransparenceGradientName;
    bool bNeedGradientFill(false);

    if (GetProperty(rXPropSet, u"FillTransparenceGradientName"_ustr)
        && (mAny >>= sFillTransparenceGradientName)
        && !sFillTransparenceGradientName.isEmpty()
        && GetProperty(rXPropSet, u"FillTransparenceGradient"_ustr))
    {
        aTransparenceGradient = model::gradient::getFromAny(mAny);
        basegfx::BColor aSingleColor;
        bNeedGradientFill = !aTransparenceGradient.GetColorStops().isSingleColor(aSingleColor);

        // we no longer need to 'guess' if FillTransparenceGradient is used by
        // comparing it's 1st color to COL_BLACK after having tested that the
        // FillTransparenceGradientName is set
        if (!bNeedGradientFill)
        {
            // Our alpha is a gray color value.
            const sal_uInt8 nRed(aSingleColor.getRed() * 255.0);

            // drawingML alpha is a percentage on a 0..100000 scale.
            nAlpha = (255 - nRed) * oox::drawingml::MAX_PERCENT / 255;
        }
    }

    // write XML
    if (bNeedGradientFill)
    {
        // no longer create copy/PseudoColorGradient, use new API of
        // WriteGradientFill to express fix fill color
        mpFS->startElementNS(XML_a, XML_gradFill, XML_rotWithShape, "0");
        WriteGradientFill(nullptr, nFillColor, &aTransparenceGradient);
        mpFS->endElementNS( XML_a, XML_gradFill );
    }
    else if ( nFillColor != nOriginalColor )
    {
        // the user has set a different color for the shape
        if (!WriteSchemeColor(u"FillComplexColor"_ustr, rXPropSet))
        {
            WriteSolidFill(::Color(ColorTransparency, nFillColor & 0xffffff), nAlpha);
        }
    }
    // tdf#91332 LO doesn't export the actual theme.xml in XLSX.
    else if ( !sColorFillScheme.isEmpty() && GetDocumentType() != DOCUMENT_XLSX )
    {
        // the shape had a scheme color and the user didn't change it
        WriteSolidFill( sColorFillScheme, aTransformations, nAlpha );
    }
    else
    {
        // the shape had a custom color and the user didn't change it
        // tdf#124013
        WriteSolidFill( ::Color(ColorTransparency, nFillColor & 0xffffff), nAlpha );
    }
}

bool DrawingML::WriteSchemeColor(OUString const& rPropertyName, const uno::Reference<beans::XPropertySet>& xPropertySet)
{
    if (!xPropertySet->getPropertySetInfo()->hasPropertyByName(rPropertyName))
        return false;

    uno::Reference<util::XComplexColor> xComplexColor;
    xPropertySet->getPropertyValue(rPropertyName) >>= xComplexColor;
    if (!xComplexColor.is())
        return false;

    auto aComplexColor = model::color::getFromXComplexColor(xComplexColor);
    if (aComplexColor.getThemeColorType() == model::ThemeColorType::Unknown)
        return false;
    const char* pColorName = g_aPredefinedClrNames[sal_Int16(aComplexColor.getThemeColorType())];
    mpFS->startElementNS(XML_a, XML_solidFill);
    mpFS->startElementNS(XML_a, XML_schemeClr, XML_val, pColorName);
    for (auto const& rTransform : aComplexColor.getTransformations())
    {
        switch (rTransform.meType)
        {
            case model::TransformationType::LumMod:
                mpFS->singleElementNS(XML_a, XML_lumMod, XML_val, OString::number(rTransform.mnValue * 10));
                break;
            case model::TransformationType::LumOff:
                mpFS->singleElementNS(XML_a, XML_lumOff, XML_val, OString::number(rTransform.mnValue * 10));
                break;
            case model::TransformationType::Tint:
                mpFS->singleElementNS(XML_a, XML_tint, XML_val, OString::number(rTransform.mnValue * 10));
                break;
            case model::TransformationType::Shade:
                mpFS->singleElementNS(XML_a, XML_shade, XML_val, OString::number(rTransform.mnValue * 10));
                break;
            default:
                break;
        }
    }
    // Alpha is actually not contained in maTransformations although possible (as of Mar 2023).
    sal_Int16 nAPITransparency(0);
    if ((rPropertyName == u"FillComplexColor" && GetProperty(xPropertySet, u"FillTransparence"_ustr))
        || (rPropertyName == u"LineComplexColor" && GetProperty(xPropertySet, u"LineTransparence"_ustr))
        || (rPropertyName == u"CharComplexColor" && GetProperty(xPropertySet, u"CharTransparence"_ustr)))
    {
        mAny >>= nAPITransparency;
    }
    if (nAPITransparency != 0)
        mpFS->singleElementNS(XML_a, XML_alpha, XML_val,
                              OString::number(MAX_PERCENT - (PER_PERCENT * nAPITransparency)));

    mpFS->endElementNS(XML_a, XML_schemeClr);
    mpFS->endElementNS(XML_a, XML_solidFill);

    return true;
}

void DrawingML::WriteGradientStop(double fOffset, const basegfx::BColor& rColor, const basegfx::BColor& rAlpha)
{
    mpFS->startElementNS(XML_a, XML_gs, XML_pos, OString::number(basegfx::fround64(fOffset * 100000)));
    WriteColor(
        ::Color(rColor),
        basegfx::fround((1.0 - rAlpha.luminance()) * oox::drawingml::MAX_PERCENT));
    mpFS->endElementNS( XML_a, XML_gs );
}

::Color DrawingML::ColorWithIntensity( sal_uInt32 nColor, sal_uInt32 nIntensity )
{
    return ::Color(ColorTransparency, ( ( ( nColor & 0xff ) * nIntensity ) / 100 )
        | ( ( ( ( ( nColor & 0xff00 ) >> 8 ) * nIntensity ) / 100 ) << 8 )
        | ( ( ( ( ( nColor & 0xff0000 ) >> 8 ) * nIntensity ) / 100 ) << 8 ));
}

void DrawingML::WriteGradientFill( const Reference< XPropertySet >& rXPropSet )
{
    if (!GetProperty(rXPropSet, u"FillGradient"_ustr))
        return;

    // use BGradient constructor directly, it will take care of Gradient/Gradient2
    basegfx::BGradient aGradient = model::gradient::getFromAny(mAny);

    // get InteropGrabBag and search the relevant attributes
    basegfx::BGradient aOriginalGradient;
    Sequence< PropertyValue > aGradientStops;
    if ( GetProperty( rXPropSet, u"InteropGrabBag"_ustr ) )
    {
        Sequence< PropertyValue > aGrabBag;
        mAny >>= aGrabBag;
        for (const auto& rProp : aGrabBag)
            if( rProp.Name == "GradFillDefinition" )
                rProp.Value >>= aGradientStops;
            else if( rProp.Name == "OriginalGradFill" )
                aOriginalGradient = model::gradient::getFromAny(rProp.Value);
    }

    // check if an ooxml gradient had been imported and if the user has modified it
    // Gradient grab-bag depends on theme grab-bag, which is implemented
    // only for DOCX.
    if (aOriginalGradient == aGradient && GetDocumentType() == DOCUMENT_DOCX)
    {
        // If we have no gradient stops that means original gradient were defined by a theme.
        if( aGradientStops.hasElements() )
        {
            mpFS->startElementNS(XML_a, XML_gradFill, XML_rotWithShape, "0");
            WriteGrabBagGradientFill(aGradientStops, aGradient);
            mpFS->endElementNS( XML_a, XML_gradFill );
        }
    }
    else
    {
        mpFS->startElementNS(XML_a, XML_gradFill, XML_rotWithShape, "0");

        basegfx::BGradient aTransparenceGradient;
        basegfx::BGradient* pTransparenceGradient(nullptr);
        double fTransparency(0.0);
        OUString sFillTransparenceGradientName;

        if (GetProperty(rXPropSet, u"FillTransparenceGradientName"_ustr)
            && (mAny >>= sFillTransparenceGradientName)
            && !sFillTransparenceGradientName.isEmpty()
            && GetProperty(rXPropSet, u"FillTransparenceGradient"_ustr))
        {
            // TransparenceGradient is only used when name is not empty
            aTransparenceGradient = model::gradient::getFromAny(mAny);
            pTransparenceGradient = &aTransparenceGradient;
        }
        else if (GetProperty(rXPropSet, u"FillTransparence"_ustr))
        {
            // no longer create PseudoTransparencyGradient, use new API of
            // WriteGradientFill to express fix transparency
            sal_Int32 nTransparency(0);
            mAny >>= nTransparency;
            // nTransparency is [0..100]%
            fTransparency = nTransparency * 0.01;
        }

        // tdf#155852 The gradient might wrongly have StepCount==0, as the draw:gradient-step-count
        // attribute in ODF does not belong to the gradient definition but is an attribute in
        // the graphic style of the shape.
        if (GetProperty(rXPropSet, u"FillGradientStepCount"_ustr))
        {
            sal_Int16 nStepCount = 0;
            mAny >>= nStepCount;
            aGradient.SetSteps(nStepCount);
        }

        WriteGradientFill(&aGradient, 0, pTransparenceGradient, fTransparency);

        mpFS->endElementNS(XML_a, XML_gradFill);
    }
}

void DrawingML::WriteGrabBagGradientFill( const Sequence< PropertyValue >& aGradientStops, const basegfx::BGradient& rBGradient )
{
    // write back the original gradient
    mpFS->startElementNS(XML_a, XML_gsLst);

    // get original stops and write them
    forconst auto& rGradientStop : aGradientStops )
    {
        Sequence< PropertyValue > aGradientStop;
        rGradientStop.Value >>= aGradientStop;

        // get values
        OUString sSchemeClr;
        double nPos = 0;
        sal_Int16 nTransparency = 0;
        ::Color nRgbClr;
        Sequence< PropertyValue > aTransformations;
        for (const auto& rProp : aGradientStop)
        {
            if( rProp.Name == "SchemeClr" )
                rProp.Value >>= sSchemeClr;
            else if( rProp.Name == "RgbClr" )
                rProp.Value >>= nRgbClr;
            else if( rProp.Name == "Pos" )
                rProp.Value >>= nPos;
            else if( rProp.Name == "Transparency" )
                rProp.Value >>= nTransparency;
            else if( rProp.Name == "Transformations" )
                rProp.Value >>= aTransformations;
        }
        // write stop
        mpFS->startElementNS(XML_a, XML_gs, XML_pos, OString::number(nPos * 100000.0));
        if( sSchemeClr.isEmpty() )
        {
            // Calculate alpha value (see oox/source/drawingml/color.cxx : getTransparency())
            sal_Int32 nAlpha = MAX_PERCENT - ( PER_PERCENT * nTransparency );
            WriteColor( nRgbClr, nAlpha );
        }
        else
        {
            WriteColor( sSchemeClr, aTransformations );
        }
        mpFS->endElementNS( XML_a, XML_gs );
    }
    mpFS->endElementNS( XML_a, XML_gsLst );

    switch (rBGradient.GetGradientStyle())
    {
        default:
        {
            const sal_Int16 nAngle(rBGradient.GetAngle());
            mpFS->singleElementNS(
                XML_a, XML_lin, XML_ang,
                OString::number(((3600 - static_cast<sal_Int32>(nAngle) + 900) * 6000) % 21600000));
            break;
        }
        case awt::GradientStyle_RADIAL:
        {
            WriteGradientPath(rBGradient, mpFS, true);
            break;
        }
    }
}

void DrawingML::WriteGradientFill(
    const basegfx::BGradient* pColorGradient, sal_Int32 nFixColor,
    const basegfx::BGradient* pTransparenceGradient, double fFixTransparence)
{
    basegfx::BColorStops aColorStops;
    basegfx::BColorStops aAlphaStops;
    basegfx::BColor aSingleColor(::Color(ColorTransparency, nFixColor).getBColor());
    basegfx::BColor aSingleAlpha(fFixTransparence);
    const basegfx::BGradient* pGradient(pColorGradient);

    if (nullptr != pColorGradient)
    {
        // extract and correct/process ColorStops
        basegfx::utils::prepareColorStops(*pColorGradient, aColorStops, aSingleColor);

        // tdf#155827 Convert 'axial' to 'linear' before synchronize and for each gradient separate.
        if (aColorStops.size() > 0 && awt::GradientStyle_AXIAL == pColorGradient->GetGradientStyle())
            aColorStops.doApplyAxial();
    }
    if (nullptr != pTransparenceGradient)
    {
        // remember basic Gradient definition to use
        // So we can get the gradient geometry in any case from pGradient.
        if (nullptr == pGradient)
        {
            pGradient = pTransparenceGradient;
        }

        // extract and correct/process AlphaStops
        basegfx::utils::prepareColorStops(*pTransparenceGradient, aAlphaStops, aSingleAlpha);
        if (aAlphaStops.size() > 0
            && awt::GradientStyle_AXIAL == pTransparenceGradient->GetGradientStyle())
        {
            aAlphaStops.doApplyAxial();
        }
    }

    if (nullptr == pGradient)
    {
        // an error - see comment in header - is to give neither pColorGradient
        // nor pTransparenceGradient
        assert(false && "pColorGradient or pTransparenceGradient should be set");
        return;
    }

    // Apply steps if used. That increases the number of stops and thus needs to be done before
    // synchronize.
    if (pGradient->GetSteps())
    {
        aColorStops.doApplySteps(pGradient->GetSteps());
        // transparency gradients are always automatic, so do not have steps.
    }

    // synchronize ColorStops and AlphaStops as preparation to export
    // so also gradients 'coupled' indirectly using the 'FillTransparenceGradient'
    // method (at import time) will be exported again.
    basegfx::utils::synchronizeColorStops(aColorStops, aAlphaStops, aSingleColor, aSingleAlpha);

    if (aColorStops.size() != aAlphaStops.size())
    {
        // this is an error - synchronizeColorStops above *has* to create that
        // state, see description there (!)
        assert(false && "oox::WriteGradientFill: non-synchronized gradients (!)");
        return;
    }

    bool bLinearOrAxial(awt::GradientStyle_LINEAR == pGradient->GetGradientStyle()
                        || awt::GradientStyle_AXIAL == pGradient->GetGradientStyle());
    if (!bLinearOrAxial)
    {
        // case awt::GradientStyle_RADIAL:
        // case awt::GradientStyle_ELLIPTICAL:
        // case awt::GradientStyle_RECT:
        // case awt::GradientStyle_SQUARE:
        // all these types need the gradients to be mirrored
        aColorStops.reverseColorStops();
        aAlphaStops.reverseColorStops();
    }

    // If there were one stop, prepareColorStops() method would have cleared aColorStops, same for
    // aAlphaStops. In case of empty stops vectors synchronizeColorStops() method creates two stops
    // for each. So at this point we have at least two stops and can fulfill the requirement of
    // <gsLst> element to have at least two child elements.

    // export GradientStops (with alpha)
    mpFS->startElementNS(XML_a, XML_gsLst);

    basegfx::BColorStops::const_iterator aCurrColor(aColorStops.begin());
    basegfx::BColorStops::const_iterator aCurrAlpha(aAlphaStops.begin());

    while (aCurrColor != aColorStops.end() && aCurrAlpha != aAlphaStops.end())
    {
        WriteGradientStop(
            aCurrColor->getStopOffset(),
            aCurrColor->getStopColor(),
            aCurrAlpha->getStopColor());
        aCurrColor++;
        aCurrAlpha++;
    }

    mpFS->endElementNS( XML_a, XML_gsLst );

    if (bLinearOrAxial)
    {
        // CT_LinearShadeProperties, cases where gradient rotation has to be exported
        // 'scaled' does not exist in LO, so only 'ang'.
        const sal_Int16 nAngle(pGradient->GetAngle());
        mpFS->singleElementNS(
            XML_a, XML_lin, XML_ang,
            OString::number(((3600 - static_cast<sal_Int32>(nAngle) + 900) * 6000) % 21600000));
    }
    else
    {
        // CT_PathShadeProperties, cases where gradient path has to be exported
        // Concentric fill is not yet implemented, therefore no type 'shape', only 'circle' or 'rect'
        const bool bCircle(pGradient->GetGradientStyle() == awt::GradientStyle_RADIAL ||
            pGradient->GetGradientStyle() == awt::GradientStyle_ELLIPTICAL);
        WriteGradientPath(*pGradient, mpFS, bCircle);
    }
}

void DrawingML::WriteLineArrow( const Reference< XPropertySet >& rXPropSet, bool bLineStart )
{
    ESCHER_LineEnd eLineEnd;
    sal_Int32 nArrowLength;
    sal_Int32 nArrowWidth;

    if ( !EscherPropertyContainer::GetLineArrow( bLineStart, rXPropSet, eLineEnd, nArrowLength, nArrowWidth ) )
        return;

    const char* len;
    const char* type;
    const char* width;

    switch( nArrowLength )
    {
        case ESCHER_LineShortArrow:
            len = "sm";
            break;
        default:
        case ESCHER_LineMediumLenArrow:
            len = "med";
            break;
        case ESCHER_LineLongArrow:
            len = "lg";
            break;
    }

    switch( eLineEnd )
    {
        default:
        case ESCHER_LineNoEnd:
            type = "none";
            break;
        case ESCHER_LineArrowEnd:
            type = "triangle";
            break;
        case ESCHER_LineArrowStealthEnd:
            type = "stealth";
            break;
        case ESCHER_LineArrowDiamondEnd:
            type = "diamond";
            break;
        case ESCHER_LineArrowOvalEnd:
            type = "oval";
            break;
        case ESCHER_LineArrowOpenEnd:
            type = "arrow";
            break;
    }

    switch( nArrowWidth )
    {
        case ESCHER_LineNarrowArrow:
            width = "sm";
            break;
        default:
        case ESCHER_LineMediumWidthArrow:
            width = "med";
            break;
        case ESCHER_LineWideArrow:
            width = "lg";
            break;
    }

    mpFS->singleElementNS( XML_a, bLineStart ? XML_headEnd : XML_tailEnd,
                           XML_len, len,
                           XML_type, type,
                           XML_w, width );
}

void DrawingML::WriteOutline( const Reference<XPropertySet>& rXPropSet, Reference< frame::XModel > const & xModel )
{
    drawing::LineStyle aLineStyle( drawing::LineStyle_NONE );
    if (GetProperty(rXPropSet, u"LineStyle"_ustr))
        mAny >>= aLineStyle;

    const LineCap aLineCap = GetProperty(rXPropSet, u"LineCap"_ustr) ? mAny.get<drawing::LineCap>() : LineCap_BUTT;

    sal_uInt32 nLineWidth = 0;
    sal_uInt32 nEmuLineWidth = 0;
    ::Color nColor;
    sal_Int32 nColorAlpha = MAX_PERCENT;
    bool bColorSet = false;
    const char* cap = nullptr;
    drawing::LineDash aLineDash;
    bool bDashSet = false;
    bool bNoFill = false;


    // get InteropGrabBag and search the relevant attributes
    OUString sColorFillScheme;
    ::Color aResolvedColorFillScheme;

    ::Color nOriginalColor;
    ::Color nStyleColor;
    sal_uInt32 nStyleLineWidth = 0;

    Sequence<PropertyValue> aStyleProperties;
    Sequence<PropertyValue> aTransformations;

    drawing::LineStyle aStyleLineStyle(drawing::LineStyle_NONE);
    drawing::LineJoint aStyleLineJoint(drawing::LineJoint_NONE);

    if (GetProperty(rXPropSet, u"InteropGrabBag"_ustr))
    {
        Sequence<PropertyValue> aGrabBag;
        mAny >>= aGrabBag;

        for (const auto& rProp : aGrabBag)
        {
            if( rProp.Name == "SpPrLnSolidFillSchemeClr" )
                rProp.Value >>= sColorFillScheme;
            if( rProp.Name == "SpPrLnSolidFillResolvedSchemeClr" )
                rProp.Value >>= aResolvedColorFillScheme;
            else if( rProp.Name == "OriginalLnSolidFillClr" )
                rProp.Value >>= nOriginalColor;
            else if( rProp.Name == "StyleLnRef" )
                rProp.Value >>= aStyleProperties;
            else if( rProp.Name == "SpPrLnSolidFillSchemeClrTransformations" )
                rProp.Value >>= aTransformations;
            else if( rProp.Name == "EmuLineWidth" )
                rProp.Value >>= nEmuLineWidth;
        }
        for (const auto& rStyleProp : aStyleProperties)
        {
            if( rStyleProp.Name == "Color" )
                rStyleProp.Value >>= nStyleColor;
            else if( rStyleProp.Name == "LineStyle" )
                rStyleProp.Value >>= aStyleLineStyle;
            else if( rStyleProp.Name == "LineJoint" )
                rStyleProp.Value >>= aStyleLineJoint;
            else if( rStyleProp.Name == "LineWidth" )
                rStyleProp.Value >>= nStyleLineWidth;
        }
    }

    if (GetProperty(rXPropSet, u"LineWidth"_ustr))
        mAny >>= nLineWidth;

    switch (aLineStyle)
    {
        case drawing::LineStyle_NONE:
            bNoFill = true;
            break;
        case drawing::LineStyle_DASH:
            if (GetProperty(rXPropSet, u"LineDash"_ustr))
            {
                aLineDash = mAny.get<drawing::LineDash>();
                //this query is good for shapes, but in the case of charts it returns 0 values
                if (aLineDash.Dots == 0 && aLineDash.DotLen == 0 && aLineDash.Dashes == 0 && aLineDash.DashLen == 0 && aLineDash.Distance == 0) {
                    OUString aLineDashName;
                    if (GetProperty(rXPropSet, u"LineDashName"_ustr))
                        mAny >>= aLineDashName;
                    if (!aLineDashName.isEmpty() && xModel) {
                        css::uno::Any aAny = getLineDash(xModel, aLineDashName);
                        aAny >>= aLineDash;
                    }
                }
            }
            else
            {
                //export the linestyle of chart wall (plot area) and chart page
                OUString aLineDashName;
                if (GetProperty(rXPropSet, u"LineDashName"_ustr))
                    mAny >>= aLineDashName;
                if (!aLineDashName.isEmpty() && xModel) {
                    css::uno::Any aAny = getLineDash(xModel, aLineDashName);
                    aAny >>= aLineDash;
                }
            }
            bDashSet = true;
            if (aLineDash.Style == DashStyle_ROUND || aLineDash.Style == DashStyle_ROUNDRELATIVE)
            {
                cap = "rnd";
            }

            SAL_INFO("oox.shape""dash dots: " << aLineDash.Dots << " dashes: " << aLineDash.Dashes
                    << " dotlen: " << aLineDash.DotLen << " dashlen: " << aLineDash.DashLen << " distance: " <<  aLineDash.Distance);

            [[fallthrough]];
        case drawing::LineStyle_SOLID:
        default:
            if (GetProperty(rXPropSet, u"LineColor"_ustr))
            {
                nColor = ::Color(ColorTransparency, mAny.get<sal_uInt32>() & 0xffffff);
                bColorSet = true;
            }
            if (GetProperty(rXPropSet, u"LineTransparence"_ustr))
            {
                nColorAlpha = MAX_PERCENT - (mAny.get<sal_Int16>() * PER_PERCENT);
            }
            if (aLineCap == LineCap_ROUND)
                cap = "rnd";
            else if (aLineCap == LineCap_SQUARE)
                 cap = "sq";
            break;
    }

    // if the line-width was not modified after importing then the original EMU value will be exported to avoid unexpected conversion (rounding) error
    if (nEmuLineWidth == 0 || static_cast<sal_uInt32>(oox::drawingml::convertEmuToHmm(nEmuLineWidth)) != nLineWidth)
        nEmuLineWidth = oox::drawingml::convertHmmToEmu(nLineWidth);
    mpFS->startElementNS( XML_a, XML_ln,
                          XML_cap, cap,
                          XML_w, sax_fastparser::UseIf(OString::number(nEmuLineWidth),
                              nLineWidth == 0 || GetDocumentType() == DOCUMENT_XLSX    // tdf#119565 LO doesn't export the actual theme.xml in XLSX.
                                  || (nLineWidth > 1 && nStyleLineWidth != nLineWidth)));

    if( bColorSet )
    {
        if( nColor != nOriginalColor )
        {
            // the user has set a different color for the line
            if (!WriteSchemeColor(u"LineComplexColor"_ustr, rXPropSet))
                WriteSolidFill(nColor, nColorAlpha);
        }
        else if( !sColorFillScheme.isEmpty() )
        {
            // the line had a scheme color and the user didn't change it
            WriteSolidFill( aResolvedColorFillScheme, aTransformations );
        }
        else
        {
            WriteSolidFill( nColor, nColorAlpha );
        }
    }

    if( bDashSet && aStyleLineStyle != drawing::LineStyle_DASH )
    {
        // Try to detect if it might come from ms preset line style import.
        // MS Office styles are always relative, both binary and OOXML.
        // "dot" is always the first dash and "dash" the second one. All OOXML presets linestyles
        // start with the longer one. Definitions are in OOXML part 1, 20.1.10.49
        // The tests are strict, for to not catch styles from standard.sod (as of Aug 2019).
        bool bIsConverted = false;

        bool bIsRelative(aLineDash.Style == DashStyle_RECTRELATIVE || aLineDash.Style == DashStyle_ROUNDRELATIVE);
        if ( bIsRelative && aLineDash.Dots == 1)
        {   // The length were tweaked on import in case of prstDash. Revert it here.
            sal_uInt32 nDotLen = aLineDash.DotLen;
            sal_uInt32 nDashLen = aLineDash.DashLen;
            sal_uInt32 nDistance = aLineDash.Distance;
            if (aLineCap != LineCap_BUTT && nDistance >= 99)
            {
                nDistance -= 99;
                nDotLen += 99;
                if (nDashLen > 0)
                    nDashLen += 99;
            }
            // LO uses length 0 for 100%, if the attribute is missing in ODF.
            // Other applications might write 100%. Make is unique for the conditions.
            if (nDotLen == 0)
                nDotLen = 100;
            if (nDashLen == 0 && aLineDash.Dashes > 0)
                nDashLen = 100;
            bIsConverted = true;
            if (nDotLen == 100 && aLineDash.Dashes == 0 && nDashLen == 0 && nDistance == 300)
            {
                mpFS->singleElementNS(XML_a, XML_prstDash, XML_val, "dot");
            }
            else if (nDotLen == 400 && aLineDash.Dashes == 0 && nDashLen == 0 && nDistance == 300)
            {
                mpFS->singleElementNS(XML_a, XML_prstDash, XML_val, "dash");
            }
            else if (nDotLen == 400 && aLineDash.Dashes == 1 && nDashLen == 100 && nDistance == 300)
            {
                mpFS->singleElementNS(XML_a, XML_prstDash, XML_val, "dashDot");
            }
            else if (nDotLen == 800 && aLineDash.Dashes == 0 && nDashLen == 0 && nDistance == 300)
            {
                mpFS->singleElementNS(XML_a, XML_prstDash, XML_val, "lgDash");
            }
            else if (nDotLen == 800 && aLineDash.Dashes == 1 && nDashLen == 100 && nDistance == 300)
            {
                mpFS->singleElementNS(XML_a, XML_prstDash, XML_val, "lgDashDot");
            }
            else if (nDotLen == 800 && aLineDash.Dashes == 2 && nDashLen == 100 && nDistance == 300)
            {
                mpFS->singleElementNS(XML_a, XML_prstDash, XML_val, "lgDashDotDot");
            }
            else if (nDotLen == 100 && aLineDash.Dashes == 0 && nDashLen == 0 && nDistance == 100)
            {
                mpFS->singleElementNS(XML_a, XML_prstDash, XML_val, "sysDot");
            }
            else if (nDotLen == 300 && aLineDash.Dashes == 0 && nDashLen == 0 && nDistance == 100)
            {
                mpFS->singleElementNS(XML_a, XML_prstDash, XML_val, "sysDash");
            }
            else if (nDotLen == 300 && aLineDash.Dashes == 1 && nDashLen == 100 && nDistance == 100)
            {
                mpFS->singleElementNS(XML_a, XML_prstDash, XML_val, "sysDashDot");
            }
            else if (nDotLen == 300 && aLineDash.Dashes == 2 && nDashLen == 100 && nDistance == 100)
            {
                mpFS->singleElementNS(XML_a, XML_prstDash, XML_val, "sysDashDotDot");
            }
            else
                bIsConverted = false;
        }
        // Do not map our own line styles to OOXML prstDash values, because custDash gives better results.
        if (!bIsConverted)
        {
            mpFS->startElementNS(XML_a, XML_custDash);
            // In case of hairline we would need the current pixel size. Instead use a reasonable
            // ersatz for it. The value is the same as SMALLEST_DASH_WIDTH in xattr.cxx.
            // (And it makes sure fLineWidth is not zero in below division.)
            double fLineWidth = nLineWidth > 0 ? nLineWidth : 26.95;
            int i;
            double fSp = bIsRelative ? aLineDash.Distance : aLineDash.Distance * 100.0 / fLineWidth;
            // LO uses line width, in case Distance is zero. MS Office would use a space of zero length.
            // So set 100% explicitly.
            if (aLineDash.Distance <= 0)
                    fSp = 100.0;
            // In case of custDash, round caps are included in dash length in MS Office. Square caps are added
            // to dash length, same as in ODF. Change the length values accordingly.
            if (aLineCap == LineCap_ROUND && fSp > 99.0)
                fSp -= 99.0;

            if (aLineDash.Dots > 0)
            {
                double fD = bIsRelative ? aLineDash.DotLen : aLineDash.DotLen * 100.0 / fLineWidth;
                // LO sets length to 0, if attribute is missing in ODF. Then a relative length of 100% is intended.
                if (aLineDash.DotLen == 0)
                    fD = 100.0;
                // Tweak dash length, see above.
                if (aLineCap == LineCap_ROUND && fSp > 99.0)
                    fD += 99.0;

                for( i = 0; i < aLineDash.Dots; i ++ )
                {
                    mpFS->singleElementNS( XML_a, XML_ds,
                                           XML_d , write1000thOfAPercent(fD),
                                           XML_sp, write1000thOfAPercent(fSp) );
                }
            }
            if ( aLineDash.Dashes > 0 )
            {
                double fD = bIsRelative ? aLineDash.DashLen : aLineDash.DashLen * 100.0 / fLineWidth;
                // LO sets length to 0, if attribute is missing in ODF. Then a relative length of 100% is intended.
                if (aLineDash.DashLen == 0)
                    fD = 100.0;
                // Tweak dash length, see above.
                if (aLineCap == LineCap_ROUND && fSp > 99.0)
                    fD += 99.0;

                for( i = 0; i < aLineDash.Dashes; i ++ )
                {
                    mpFS->singleElementNS( XML_a , XML_ds,
                                           XML_d , write1000thOfAPercent(fD),
                                           XML_sp, write1000thOfAPercent(fSp) );
                }
            }

            SAL_WARN_IF(nLineWidth <= 0,
                        "oox.shape""while writing outline - custom dash - line width was < 0 : " << nLineWidth);
            SAL_WARN_IF(aLineDash.Dashes < 0,
                        "oox.shape""while writing outline - custom dash - number of dashes was < 0 : " << aLineDash.Dashes);
            SAL_WARN_IF(aLineDash.Dashes > 0 && aLineDash.DashLen <= 0,
                        "oox.shape""while writing outline - custom dash - dash length was < 0 : " << aLineDash.DashLen);
            SAL_WARN_IF(aLineDash.Dots < 0,
                        "oox.shape""while writing outline - custom dash - number of dots was < 0 : " << aLineDash.Dots);
            SAL_WARN_IF(aLineDash.Dots > 0 && aLineDash.DotLen <= 0,
                        "oox.shape""while writing outline - custom dash - dot length was < 0 : " << aLineDash.DotLen);
            SAL_WARN_IF(aLineDash.Distance <= 0,
                        "oox.shape""while writing outline - custom dash - distance was < 0 : " << aLineDash.Distance);

            mpFS->endElementNS( XML_a, XML_custDash );
        }
    }

    if (!bNoFill && nLineWidth > 1 && GetProperty(rXPropSet, u"LineJoint"_ustr))
    {
        LineJoint eLineJoint = mAny.get<LineJoint>();

        // tdf#119565 LO doesn't export the actual theme.xml in XLSX.
        if (aStyleLineJoint == LineJoint_NONE || GetDocumentType() == DOCUMENT_XLSX
            || aStyleLineJoint != eLineJoint)
        {
            // style-defined line joint does not exist, or is different from the shape's joint
            switch( eLineJoint )
            {
                case LineJoint_NONE:
                case LineJoint_BEVEL:
                    mpFS->singleElementNS(XML_a, XML_bevel);
                    break;
                default:
                case LineJoint_MIDDLE:
                case LineJoint_MITER:
                    mpFS->singleElementNS(XML_a, XML_miter);
                    break;
                case LineJoint_ROUND:
                    mpFS->singleElementNS(XML_a, XML_round);
                    break;
            }
        }
    }

    if( !bNoFill )
    {
        WriteLineArrow( rXPropSet, true );
        WriteLineArrow( rXPropSet, false );
    }
    else
    {
        mpFS->singleElementNS(XML_a, XML_noFill);
    }

    mpFS->endElementNS( XML_a, XML_ln );
}

OUString DrawingML::GetComponentDir() const
{
    return OUString(getComponentDir(meDocumentType));
}

OUString DrawingML::GetRelationCompPrefix() const
{
    return OUString(getRelationCompPrefix(meDocumentType));
}

void GraphicExport::writeSvgExtension(OUString const& rSvgRelId)
{
    if (rSvgRelId.isEmpty())
        return;

    mpFS->startElementNS(XML_a, XML_extLst);
    mpFS->startElementNS(XML_a, XML_ext, XML_uri, "{96DAC541-7B7A-43D3-8B79-37D633B846F1}");
    mpFS->singleElementNS(XML_asvg, XML_svgBlip,
            FSNS(XML_xmlns, XML_asvg), mpFilterBase->getNamespaceURL(OOX_NS(asvg)),
            FSNS(XML_r, XML_embed), rSvgRelId);
    mpFS->endElementNS(XML_a, XML_ext);
    mpFS->endElementNS( XML_a, XML_extLst);
}

void GraphicExport::writeBlip(Graphic const& rGraphic, std::vector<model::BlipEffect> const& rEffects)
{
    OUString sRelId = writeToStorage(rGraphic, /*bRelPathToMedia*/false);

    mpFS->startElementNS(XML_a, XML_blip, FSNS(XML_r, XML_embed), sRelId);

    auto const& rVectorGraphicDataPtr = rGraphic.getVectorGraphicData();

    if (rVectorGraphicDataPtr && rVectorGraphicDataPtr->getType() == VectorGraphicDataType::Svg)
    {
        OUString sSvgRelId = writeToStorage(rGraphic, /*bRelPathToMedia*/false, TypeHint::SVG);
        writeSvgExtension(sSvgRelId);
    }

    for (auto const& rEffect : rEffects)
    {
        switch (rEffect.meType)
        {
            case model::BlipEffectType::AlphaBiLevel:
            {
                mpFS->singleElementNS(XML_a, XML_alphaBiLevel, XML_thresh, OString::number(rEffect.mnThreshold));
            }
            break;
            case model::BlipEffectType::AlphaCeiling:
            {
                mpFS->singleElementNS(XML_a, XML_alphaCeiling);
            }
            break;
            case model::BlipEffectType::AlphaFloor:
            {
                mpFS->singleElementNS(XML_a, XML_alphaFloor);
            }
            break;
            case model::BlipEffectType::AlphaInverse:
            {
                mpFS->singleElementNS(XML_a, XML_alphaInv);
                // TODO: export rEffect.maColor1
            }
            break;
            case model::BlipEffectType::AlphaModulate:
            {
                mpFS->singleElementNS(XML_a, XML_alphaMod);
                // TODO
            }
            break;
            case model::BlipEffectType::AlphaModulateFixed:
            {
                mpFS->singleElementNS(XML_a, XML_alphaModFix, XML_amt, OString::number(rEffect.mnAmount));
            }
            break;
            case model::BlipEffectType::AlphaReplace:
            {
                mpFS->singleElementNS(XML_a, XML_alphaRepl, XML_a, OString::number(rEffect.mnAlpha));
            }
            break;
            case model::BlipEffectType::BiLevel:
            {
                mpFS->singleElementNS(XML_a, XML_biLevel, XML_thresh, OString::number(rEffect.mnThreshold));
            }
            break;
            case model::BlipEffectType::Blur:
            {
                mpFS->singleElementNS(XML_a, XML_blur,
                    XML_rad, OString::number(rEffect.mnRadius),
                    XML_grow, rEffect.mbGrow ? "1" : "0");
            }
            break;
            case model::BlipEffectType::ColorChange:
            {
                mpFS->startElementNS(XML_a, XML_clrChange, XML_useA, rEffect.mbUseAlpha ? "1" : "0");
                mpFS->endElementNS(XML_a, XML_clrChange);
            }
            break;
            case model::BlipEffectType::ColorReplace:
            {
                mpFS->startElementNS(XML_a, XML_clrRepl);
                mpFS->endElementNS(XML_a, XML_clrRepl);
            }
            break;
            case model::BlipEffectType::DuoTone:
            {
                mpFS->startElementNS(XML_a, XML_duotone);
                mpFS->endElementNS(XML_a, XML_duotone);
            }
            break;
            case model::BlipEffectType::FillOverlay:
            {
                mpFS->singleElementNS(XML_a, XML_fillOverlay);
            }
            break;
            case model::BlipEffectType::Grayscale:
            {
                mpFS->singleElementNS(XML_a, XML_grayscl);
            }
            break;
            case model::BlipEffectType::HSL:
            {
                mpFS->singleElementNS(XML_a, XML_hsl,
                    XML_hue, OString::number(rEffect.mnHue),
                    XML_sat, OString::number(rEffect.mnSaturation),
                    XML_lum, OString::number(rEffect.mnLuminance));
            }
            break;
            case model::BlipEffectType::Luminance:
            {
                mpFS->singleElementNS(XML_a, XML_lum,
                    XML_bright, OString::number(rEffect.mnBrightness),
                    XML_contrast, OString::number(rEffect.mnContrast));
            }
            break;
            case model::BlipEffectType::Tint:
            {
                mpFS->singleElementNS(XML_a, XML_tint,
                    XML_hue, OString::number(rEffect.mnHue),
                    XML_amt, OString::number(rEffect.mnAmount));
            }
            break;

            default:
                break;
        }
    }

    mpFS->endElementNS(XML_a, XML_blip);
}

OUString GraphicExport::writeNewEntryToStorage(const Graphic& rGraphic, bool bRelPathToMedia)
{
    GfxLink const aLink = rGraphic.GetGfxLink();

    OUString sMediaType;
    OUString aExtension;

    SvMemoryStream aStream;
    const void* aData = aLink.GetData();
    std::size_t nDataSize = aLink.GetDataSize();

    switch (aLink.GetType())
    {
        case GfxLinkType::NativeGif:
            sMediaType = u"image/gif"_ustr;
            aExtension = u"gif"_ustr;
            break;

        // #i15508# added BMP type for better exports
        // export not yet active, so adding for reference (not checked)
        case GfxLinkType::NativeBmp:
            sMediaType = u"image/bmp"_ustr;
            aExtension = u"bmp"_ustr;
            break;

        case GfxLinkType::NativeJpg:
            sMediaType = u"image/jpeg"_ustr;
            aExtension = u"jpeg"_ustr;
            break;
        case GfxLinkType::NativePng:
            sMediaType = u"image/png"_ustr;
            aExtension = u"png"_ustr;
            break;
        case GfxLinkType::NativeTif:
            sMediaType = u"image/tiff"_ustr;
            aExtension = u"tif"_ustr;
            break;
        case GfxLinkType::NativeWmf:
            sMediaType = u"image/x-wmf"_ustr;
            aExtension = u"wmf"_ustr;
            break;
        case GfxLinkType::NativeMet:
            sMediaType = u"image/x-met"_ustr;
            aExtension = u"met"_ustr;
            break;
        case GfxLinkType::NativePct:
            sMediaType = u"image/x-pict"_ustr;
            aExtension = u"pct"_ustr;
            break;
        case GfxLinkType::NativeMov:
            sMediaType = u"application/movie"_ustr;
            aExtension = u"MOV"_ustr;
            break;
        default:
        {
            GraphicType aType = rGraphic.GetType();
            if (aType == GraphicType::Bitmap || aType == GraphicType::GdiMetafile)
            {
                if (aType == GraphicType::Bitmap)
                {
                    (void)GraphicConverter::Export(aStream, rGraphic, ConvertDataFormat::PNG);
                    sMediaType = u"image/png"_ustr;
                    aExtension = u"png"_ustr;
                }
                else
                {
                    (void)GraphicConverter::Export(aStream, rGraphic, ConvertDataFormat::EMF);
                    sMediaType = u"image/x-emf"_ustr;
                    aExtension = u"emf"_ustr;
                }
            }
            else
            {
                SAL_WARN("oox.shape""unhandled graphic type " << static_cast<int>(aType));

                /*Earlier, even in case of unhandled graphic types we were
                  proceeding to write the image, which would eventually
                  write an empty image with a zero size, and return a valid
                  relationID, which is incorrect.
                  */

                return OUString();
            }

            aData = aStream.GetData();
            nDataSize = aStream.GetEndOfData();
        }
        break;
    }

    GraphicExportCache& rGraphicExportCache = GraphicExportCache::get();
    auto sImageCountString = OUString::number(rGraphicExportCache.nextImageCount());

    OUString sComponentDir(getComponentDir(meDocumentType));

    OUString sImagePath = sComponentDir + u"/media/image"_ustr + sImageCountString + u"."_ustr + aExtension;

    Reference<XOutputStream> xOutStream = mpFilterBase->openFragmentStream(sImagePath, sMediaType);
    xOutStream->writeBytes(Sequence<sal_Int8>(static_cast<const sal_Int8*>(aData), nDataSize));
    xOutStream->closeOutput();

    OUString sRelationCompPrefix;
    if (bRelPathToMedia)
        sRelationCompPrefix = u"../"_ustr;
    else
        sRelationCompPrefix = getRelationCompPrefix(meDocumentType);

    OUString sPath = sRelationCompPrefix + u"media/image"_ustr + sImageCountString + u"."_ustr + aExtension;

    rGraphicExportCache.addExportGraphics(rGraphic.GetChecksum(), sPath);

    return sPath;
}

namespace
{
BitmapChecksum makeChecksumUniqueForSVG(BitmapChecksum aChecksum)
{
    // need to modify the checksum so we know it's for SVG - just invert it
    return ~aChecksum;
}

// end anonymous namespace

OUString GraphicExport::writeNewSvgEntryToStorage(const Graphic& rGraphic, bool bRelPathToMedia)
{
    OUString sMediaType = u"image/svg"_ustr;
    OUString aExtension = u"svg"_ustr;

    GfxLink const aLink = rGraphic.GetGfxLink();
    if (aLink.GetType() != GfxLinkType::NativeSvg)
        return OUString();

    const void* aData = aLink.GetData();
    std::size_t nDataSize = aLink.GetDataSize();

    GraphicExportCache& rGraphicExportCache = GraphicExportCache::get();
    auto sImageCountString = OUString::number(rGraphicExportCache.nextImageCount());

    OUString sComponentDir(getComponentDir(meDocumentType));

    OUString sImagePath = sComponentDir + u"/media/image"_ustr + sImageCountString + u"."_ustr + aExtension;

    Reference<XOutputStream> xOutStream = mpFilterBase->openFragmentStream(sImagePath, sMediaType);
    xOutStream->writeBytes(Sequence<sal_Int8>(static_cast<const sal_Int8*>(aData), nDataSize));
    xOutStream->closeOutput();

    OUString sRelationCompPrefix;
    if (bRelPathToMedia)
        sRelationCompPrefix = u"../"_ustr;
    else
        sRelationCompPrefix = getRelationCompPrefix(meDocumentType);

    OUString sPath = sRelationCompPrefix + u"media/image"_ustr + sImageCountString + u"."_ustr + aExtension;

    rGraphicExportCache.addExportGraphics(makeChecksumUniqueForSVG(rGraphic.GetChecksum()), sPath);

    return sPath;
}

OUString GraphicExport::writeToStorage(const Graphic& rGraphic, bool bRelPathToMedia, TypeHint eHint)
{
    OUString sPath;

    auto aChecksum = rGraphic.GetChecksum();
    if (eHint == TypeHint::SVG)
        aChecksum = makeChecksumUniqueForSVG(aChecksum);

    GraphicExportCache& rGraphicExportCache = GraphicExportCache::get();
    sPath = rGraphicExportCache.findExportGraphics(aChecksum);

    if (sPath.isEmpty())
    {
        if (eHint == TypeHint::SVG)
            sPath = writeNewSvgEntryToStorage(rGraphic, bRelPathToMedia);
        else
            sPath = writeNewEntryToStorage(rGraphic, bRelPathToMedia);

        if (sPath.isEmpty())
            return OUString(); // couldn't store
    }

    OUString sRelId = mpFilterBase->addRelation(mpFS->getOutputStream(), oox::getRelationship(Relationship::IMAGE), sPath);

    return sRelId;
}

std::shared_ptr<GraphicExport> DrawingML::createGraphicExport()
{
    return std::make_shared<GraphicExport>(mpFS, mpFB, meDocumentType);
}

OUString DrawingML::writeGraphicToStorage(const Graphic& rGraphic , bool bRelPathToMedia, GraphicExport::TypeHint eHint)
{
    GraphicExport aExporter(mpFS, mpFB, meDocumentType);
    return aExporter.writeToStorage(rGraphic, bRelPathToMedia, eHint);
}

void DrawingML::WriteMediaNonVisualProperties(const css::uno::Reference<css::drawing::XShape>& xShape)
{
    SdrMediaObj* pMediaObj = dynamic_cast<SdrMediaObj*>(SdrObject::getSdrObjectFromXShape(xShape));
    if (!pMediaObj)
        return;

    // extension
    OUString aExtension;
    const OUString& rURL(pMediaObj->getURL());
    int nLastDot = rURL.lastIndexOf('.');
    if (nLastDot >= 0)
        aExtension = rURL.copy(nLastDot).replace(':''_'); // Colons are not allowed in Zip entry file names, see OStorageHelper::IsValidZipEntryFileName

    bool bEmbed = rURL.startsWith("vnd.sun.star.Package:");
    Relationship eMediaType = Relationship::VIDEO;

    // mime type
#if HAVE_FEATURE_AVMEDIA
    OUString aMimeType(pMediaObj->getMediaProperties().getMimeType());
#else
    OUString aMimeType("none");
#endif
    if (aMimeType.startsWith("audio/"))
    {
        eMediaType = Relationship::AUDIO;
    }
    else
    if (aMimeType == "application/vnd.sun.star.media")
    {
        // try to set something better
        // TODO fix the importer to actually set the mimetype on import
        if (aExtension.equalsIgnoreAsciiCase(".avi"))
            aMimeType = "video/x-msvideo";
        else if (aExtension.equalsIgnoreAsciiCase(".flv"))
            aMimeType = "video/x-flv";
        else if (aExtension.equalsIgnoreAsciiCase(".mp4"))
            aMimeType = "video/mp4";
        else if (aExtension.equalsIgnoreAsciiCase(".mov"))
            aMimeType = "video/quicktime";
        else if (aExtension.equalsIgnoreAsciiCase(".ogv"))
            aMimeType = "video/ogg";
        else if (aExtension.equalsIgnoreAsciiCase(".wmv"))
            aMimeType = "video/x-ms-wmv";
        else if (aExtension.equalsIgnoreAsciiCase(".wav"))
        {
            aMimeType = "audio/x-wav";
            eMediaType = Relationship::AUDIO;
        }
        else if (aExtension.equalsIgnoreAsciiCase(".m4a"))
        {
            aMimeType = "audio/mp4";
            eMediaType = Relationship::AUDIO;
        }
        else if (aExtension.equalsIgnoreAsciiCase(".mp3"))
        {
            aMimeType = "audio/mp3";
            eMediaType = Relationship::AUDIO;
        }
    }

    OUString aVideoFileRelId;
    OUString aMediaRelId;

    if (bEmbed)
    {
        sal_Int32  nImageCount = GraphicExportCache::get().nextImageCount();

        OUString sFileName = GetComponentDir() + u"/media/media"_ustr + OUString::number(nImageCount) + aExtension;

        // copy the video stream
        Reference<XOutputStream> xOutStream = mpFB->openFragmentStream(sFileName, aMimeType);

        uno::Reference<io::XInputStream> xInputStream(pMediaObj->GetInputStream());
        comphelper::OStorageHelper::CopyInputToOutput(xInputStream, xOutStream);

        xOutStream->closeOutput();

        // create the relation
        OUString aPath = GetRelationCompPrefix() + u"media/media"_ustr + OUString::number(nImageCount) + aExtension;

        aVideoFileRelId = mpFB->addRelation(mpFS->getOutputStream(), oox::getRelationship(eMediaType), aPath);
        aMediaRelId = mpFB->addRelation(mpFS->getOutputStream(), oox::getRelationship(Relationship::MEDIA), aPath);
    }
    else
    {
        aVideoFileRelId = mpFB->addRelation(mpFS->getOutputStream(), oox::getRelationship(eMediaType), rURL, true);
        aMediaRelId = mpFB->addRelation(mpFS->getOutputStream(), oox::getRelationship(Relationship::MEDIA), rURL, true);
    }

    GetFS()->startElementNS(XML_p, XML_nvPr);

--> --------------------

--> maximum size reached

--> --------------------

Messung V0.5
C=85 H=91 G=87

¤ Dauer der Verarbeitung: 0.20 Sekunden  ¤

*© Formatika GbR, Deutschland






Wurzel

Suchen

Beweissystem der NASA

Beweissystem Isabelle

NIST Cobol Testsuite

Cephes Mathematical Library

Wiener Entwicklungsmethode

Haftungshinweis

Die Informationen auf dieser Webseite wurden nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit, noch Qualität der bereit gestellten Informationen zugesichert.

Bemerkung:

Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.