<commit_before>/* -*- 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 <iterator>
#include <boost/utility.hpp>

#include "oox/drawingml/fillproperties.hxx"

#include <com/sun/star/lang/XMultiServiceFactory.hpp>
#include <com/sun/star/beans/XPropertySet.hpp>
#include <com/sun/star/awt/Gradient.hpp>
#include <com/sun/star/text/GraphicCrop.hpp>
#include <com/sun/star/awt/Size.hpp>
#include <com/sun/star/drawing/BitmapMode.hpp>
#include <com/sun/star/drawing/ColorMode.hpp>
#include <com/sun/star/drawing/FillStyle.hpp>
#include <com/sun/star/drawing/Hatch.hpp>
#include <com/sun/star/drawing/RectanglePoint.hpp>
#include <com/sun/star/graphic/XGraphicTransformer.hpp>
#include "oox/helper/graphichelper.hxx"
#include "oox/drawingml/drawingmltypes.hxx"
#include "oox/drawingml/shapepropertymap.hxx"
#include "oox/token/tokens.hxx"

using namespace ::com::sun::star;
using namespace ::com::sun::star::drawing;
using namespace ::com::sun::star::graphic;

using ::com::sun::star::uno::Reference;
using ::com::sun::star::uno::Exception;
using ::com::sun::star::uno::UNO_QUERY;
using ::com::sun::star::uno::UNO_QUERY_THROW;
using ::com::sun::star::geometry::IntegerRectangle2D;

namespace oox {
namespace drawingml {

namespace {

Reference< XGraphic > lclCheckAndApplyDuotoneTransform( const BlipFillProperties& aBlipProps, Reference< XGraphic > xGraphic,
                                                        const GraphicHelper& rGraphicHelper, const sal_Int32 nPhClr )
{
    if( aBlipProps.maDuotoneColors[0].isUsed() && aBlipProps.maDuotoneColors[1].isUsed() )
    {
        sal_Int32 nColor1 = aBlipProps.maDuotoneColors[0].getColor( rGraphicHelper, nPhClr );
        sal_Int32 nColor2 = aBlipProps.maDuotoneColors[1].getColor( rGraphicHelper, nPhClr );
        try
        {
            Reference< XGraphicTransformer > xTransformer( aBlipProps.mxGraphic, UNO_QUERY_THROW );
            xGraphic = xTransformer->applyDuotone( xGraphic, nColor1, nColor2 );
        }
        catch( Exception& )
        {
        }
    }
    return xGraphic;
}

Reference< XGraphic > lclCheckAndApplyChangeColorTransform( const BlipFillProperties &aBlipProps, Reference< XGraphic > xGraphic,
                                                            const GraphicHelper& rGraphicHelper, const sal_Int32 nPhClr )
{
    if( aBlipProps.maColorChangeFrom.isUsed() && aBlipProps.maColorChangeTo.isUsed() )
    {
        sal_Int32 nFromColor = aBlipProps.maColorChangeFrom.getColor( rGraphicHelper, nPhClr );
        sal_Int32 nToColor = aBlipProps.maColorChangeTo.getColor( rGraphicHelper, nPhClr );
        if ( (nFromColor != nToColor) || aBlipProps.maColorChangeTo.hasTransparency() ) try
        {
            sal_Int16 nToTransparence = aBlipProps.maColorChangeTo.getTransparency();
            sal_Int8 nToAlpha = static_cast< sal_Int8 >( (100 - nToTransparence) * 2.55 );
            Reference< XGraphicTransformer > xTransformer( aBlipProps.mxGraphic, UNO_QUERY_THROW );
            xGraphic = xTransformer->colorChange( xGraphic, nFromColor, 9, nToColor, nToAlpha );
        }
        catch( Exception& )
        {
        }
    }
    return xGraphic;
}

Reference< XGraphic > applyBrightnessContrast( Reference< XGraphic > xGraphic, sal_Int32 brightness, sal_Int32 contrast )
{
    try
        {
            Reference< XGraphicTransformer > xTransformer( xGraphic, UNO_QUERY_THROW );
            xGraphic = xTransformer->applyBrightnessContrast( xGraphic, brightness, contrast, true );
        }
        catch( Exception& )
        {
        }
    return xGraphic;
}

BitmapMode lclGetBitmapMode( sal_Int32 nToken )
{
    OSL_ASSERT((nToken & sal_Int32(0xFFFF0000))==0);
    switch( nToken )
    {
        case XML_tile:      return BitmapMode_REPEAT;
        case XML_stretch:   return BitmapMode_STRETCH;
    }
    return BitmapMode_NO_REPEAT;
}

RectanglePoint lclGetRectanglePoint( sal_Int32 nToken )
{
    OSL_ASSERT((nToken & sal_Int32(0xFFFF0000))==0);
    switch( nToken )
    {
        case XML_tl:    return RectanglePoint_LEFT_TOP;
        case XML_t:     return RectanglePoint_MIDDLE_TOP;
        case XML_tr:    return RectanglePoint_RIGHT_TOP;
        case XML_l:     return RectanglePoint_LEFT_MIDDLE;
        case XML_ctr:   return RectanglePoint_MIDDLE_MIDDLE;
        case XML_r:     return RectanglePoint_RIGHT_MIDDLE;
        case XML_bl:    return RectanglePoint_LEFT_BOTTOM;
        case XML_b:     return RectanglePoint_MIDDLE_BOTTOM;
        case XML_br:    return RectanglePoint_RIGHT_BOTTOM;
    }
    return RectanglePoint_LEFT_TOP;
}

const awt::Size lclGetOriginalSize( const GraphicHelper& rGraphicHelper, const Reference< XGraphic >& rxGraphic )
{
    awt::Size aSizeHmm( 0, 0 );
    try
    {
        Reference< beans::XPropertySet > xGraphicPropertySet( rxGraphic, UNO_QUERY_THROW );
        if( xGraphicPropertySet->getPropertyValue( "Size100thMM" ) >>= aSizeHmm )
        {
            if( !aSizeHmm.Width && !aSizeHmm.Height )
            {   // MAPMODE_PIXEL USED :-(
                awt::Size aSourceSizePixel( 0, 0 );
                if( xGraphicPropertySet->getPropertyValue( "SizePixel" ) >>= aSourceSizePixel )
                    aSizeHmm = rGraphicHelper.convertScreenPixelToHmm( aSourceSizePixel );
            }
        }
    }
    catch( Exception& )
    {
    }
    return aSizeHmm;
}

} // namespace

void GradientFillProperties::assignUsed( const GradientFillProperties& rSourceProps )
{
    if( !rSourceProps.maGradientStops.empty() )
        maGradientStops = rSourceProps.maGradientStops;
    moFillToRect.assignIfUsed( rSourceProps.moFillToRect );
    moTileRect.assignIfUsed( rSourceProps.moTileRect );
    moGradientPath.assignIfUsed( rSourceProps.moGradientPath );
    moShadeAngle.assignIfUsed( rSourceProps.moShadeAngle );
    moShadeFlip.assignIfUsed( rSourceProps.moShadeFlip );
    moShadeScaled.assignIfUsed( rSourceProps.moShadeScaled );
    moRotateWithShape.assignIfUsed( rSourceProps.moRotateWithShape );
}

void PatternFillProperties::assignUsed( const PatternFillProperties& rSourceProps )
{
    maPattFgColor.assignIfUsed( rSourceProps.maPattFgColor );
    maPattBgColor.assignIfUsed( rSourceProps.maPattBgColor );
    moPattPreset.assignIfUsed( rSourceProps.moPattPreset );
}

void BlipFillProperties::assignUsed( const BlipFillProperties& rSourceProps )
{
    if( rSourceProps.mxGraphic.is() )
        mxGraphic = rSourceProps.mxGraphic;
    moBitmapMode.assignIfUsed( rSourceProps.moBitmapMode );
    moFillRect.assignIfUsed( rSourceProps.moFillRect );
    moTileOffsetX.assignIfUsed( rSourceProps.moTileOffsetX );
    moTileOffsetY.assignIfUsed( rSourceProps.moTileOffsetY );
    moTileScaleX.assignIfUsed( rSourceProps.moTileScaleX );
    moTileScaleY.assignIfUsed( rSourceProps.moTileScaleY );
    moTileAlign.assignIfUsed( rSourceProps.moTileAlign );
    moTileFlip.assignIfUsed( rSourceProps.moTileFlip );
    moRotateWithShape.assignIfUsed( rSourceProps.moRotateWithShape );
    moColorEffect.assignIfUsed( rSourceProps.moColorEffect );
    moBrightness.assignIfUsed( rSourceProps.moBrightness );
    moContrast.assignIfUsed( rSourceProps.moContrast );
    maColorChangeFrom.assignIfUsed( rSourceProps.maColorChangeFrom );
    maColorChangeTo.assignIfUsed( rSourceProps.maColorChangeTo );
    maDuotoneColors[0].assignIfUsed( rSourceProps.maDuotoneColors[0] );
    maDuotoneColors[1].assignIfUsed( rSourceProps.maDuotoneColors[1] );
    maEffect.assignUsed( rSourceProps.maEffect );
}

void FillProperties::assignUsed( const FillProperties& rSourceProps )
{
    moFillType.assignIfUsed( rSourceProps.moFillType );
    maFillColor.assignIfUsed( rSourceProps.maFillColor );
    maGradientProps.assignUsed( rSourceProps.maGradientProps );
    maPatternProps.assignUsed( rSourceProps.maPatternProps );
    maBlipProps.assignUsed( rSourceProps.maBlipProps );
}

Color FillProperties::getBestSolidColor() const
{
    Color aSolidColor;
    if( moFillType.has() ) switch( moFillType.get() )
    {
        case XML_solidFill:
            aSolidColor = maFillColor;
        break;
        case XML_gradFill:
            if( !maGradientProps.maGradientStops.empty() )
                aSolidColor = maGradientProps.maGradientStops.begin()->second;
        break;
        case XML_pattFill:
            aSolidColor = maPatternProps.maPattBgColor.isUsed() ? maPatternProps.maPattBgColor : maPatternProps.maPattFgColor;
        break;
    }
    return aSolidColor;
}

/// Maps the hatch token to drawing::Hatch.
static drawing::Hatch createHatch( sal_Int32 nHatchToken, sal_Int32 nColor )
{
    drawing::Hatch aHatch;
    aHatch.Color = nColor;

    // best-effort mapping; we do not support all the styles in core
    switch ( nHatchToken )
    {
        case XML_pct5:       aHatch.Style = drawing::HatchStyle_SINGLE; aHatch.Distance = 250; aHatch.Angle = 450;  break;
        case XML_pct10:      aHatch.Style = drawing::HatchStyle_SINGLE; aHatch.Distance = 200; aHatch.Angle = 450;  break;
        case XML_pct20:      aHatch.Style = drawing::HatchStyle_SINGLE; aHatch.Distance = 150; aHatch.Angle = 450;  break;
        case XML_pct25:      aHatch.Style = drawing::HatchStyle_DOUBLE; aHatch.Distance = 200; aHatch.Angle = 450;  break;
        case XML_pct30:      aHatch.Style = drawing::HatchStyle_DOUBLE; aHatch.Distance = 175; aHatch.Angle = 450;  break;
        case XML_pct40:      aHatch.Style = drawing::HatchStyle_DOUBLE; aHatch.Distance = 150; aHatch.Angle = 450;  break;
        case XML_pct50:      aHatch.Style = drawing::HatchStyle_DOUBLE; aHatch.Distance = 125; aHatch.Angle = 450;  break;
        case XML_pct60:      aHatch.Style = drawing::HatchStyle_TRIPLE; aHatch.Distance = 150; aHatch.Angle = 450;  break;
        case XML_pct70:      aHatch.Style = drawing::HatchStyle_TRIPLE; aHatch.Distance = 125; aHatch.Angle = 450;  break;
        case XML_pct75:      aHatch.Style = drawing::HatchStyle_TRIPLE; aHatch.Distance = 100; aHatch.Angle = 450;  break;
        case XML_pct80:      aHatch.Style = drawing::HatchStyle_TRIPLE; aHatch.Distance = 75;  aHatch.Angle = 450;  break;
        case XML_pct90:      aHatch.Style = drawing::HatchStyle_TRIPLE; aHatch.Distance = 50;  aHatch.Angle = 450;  break;
        case XML_horz:       aHatch.Style = drawing::HatchStyle_SINGLE; aHatch.Distance = 100; aHatch.Angle = 0;    break;
        case XML_vert:       aHatch.Style = drawing::HatchStyle_SINGLE; aHatch.Distance = 100; aHatch.Angle = 900;  break;
        case XML_ltHorz:     aHatch.Style = drawing::HatchStyle_SINGLE; aHatch.Distance = 50;  aHatch.Angle = 0;    break;
        case XML_ltVert:     aHatch.Style = drawing::HatchStyle_SINGLE; aHatch.Distance = 50;  aHatch.Angle = 900;  break;
        case XML_dkHorz:     aHatch.Style = drawing::HatchStyle_SINGLE; aHatch.Distance = 25;  aHatch.Angle = 0;    break;
        case XML_dkVert:     aHatch.Style = drawing::HatchStyle_SINGLE; aHatch.Distance = 25;  aHatch.Angle = 900;  break;
        case XML_narHorz:    aHatch.Style = drawing::HatchStyle_SINGLE; aHatch.Distance = 50;  aHatch.Angle = 0;    break;
        case XML_narVert:    aHatch.Style = drawing::HatchStyle_SINGLE; aHatch.Distance = 50;  aHatch.Angle = 900;  break;
        case XML_dashHorz:   aHatch.Style = drawing::HatchStyle_SINGLE; aHatch.Distance = 150; aHatch.Angle = 0;    break;
        case XML_dashVert:   aHatch.Style = drawing::HatchStyle_SINGLE; aHatch.Distance = 150; aHatch.Angle = 900;  break;
        case XML_cross:      aHatch.Style = drawing::HatchStyle_DOUBLE; aHatch.Distance = 100; aHatch.Angle = 0;    break;
        case XML_dnDiag:     aHatch.Style = drawing::HatchStyle_SINGLE; aHatch.Distance = 100; aHatch.Angle = 1350; break;
        case XML_upDiag:     aHatch.Style = drawing::HatchStyle_SINGLE; aHatch.Distance = 100; aHatch.Angle = 450;  break;
        case XML_ltDnDiag:   aHatch.Style = drawing::HatchStyle_SINGLE; aHatch.Distance = 50;  aHatch.Angle = 1350; break;
        case XML_ltUpDiag:   aHatch.Style = drawing::HatchStyle_SINGLE; aHatch.Distance = 50;  aHatch.Angle = 450;  break;
        case XML_dkDnDiag:   aHatch.Style = drawing::HatchStyle_SINGLE; aHatch.Distance = 50;  aHatch.Angle = 1350; break;
        case XML_dkUpDiag:   aHatch.Style = drawing::HatchStyle_SINGLE; aHatch.Distance = 50;  aHatch.Angle = 450;  break;
        case XML_wdDnDiag:   aHatch.Style = drawing::HatchStyle_SINGLE; aHatch.Distance = 100; aHatch.Angle = 1350; break;
        case XML_wdUpDiag:   aHatch.Style = drawing::HatchStyle_SINGLE; aHatch.Distance = 100; aHatch.Angle = 450;  break;
        case XML_dashDnDiag: aHatch.Style = drawing::HatchStyle_SINGLE; aHatch.Distance = 150; aHatch.Angle = 1350; break;
        case XML_dashUpDiag: aHatch.Style = drawing::HatchStyle_SINGLE; aHatch.Distance = 150; aHatch.Angle = 450;  break;
        case XML_diagCross:  aHatch.Style = drawing::HatchStyle_DOUBLE; aHatch.Distance = 100; aHatch.Angle = 450;  break;
        case XML_smCheck:    aHatch.Style = drawing::HatchStyle_DOUBLE; aHatch.Distance = 50;  aHatch.Angle = 450;  break;
        case XML_lgCheck:    aHatch.Style = drawing::HatchStyle_DOUBLE; aHatch.Distance = 100; aHatch.Angle = 450;  break;
        case XML_smGrid:     aHatch.Style = drawing::HatchStyle_DOUBLE; aHatch.Distance = 50;  aHatch.Angle = 0;    break;
        case XML_lgGrid:     aHatch.Style = drawing::HatchStyle_DOUBLE; aHatch.Distance = 100; aHatch.Angle = 0;    break;
        case XML_dotGrid:    aHatch.Style = drawing::HatchStyle_DOUBLE; aHatch.Distance = 400; aHatch.Angle = 0;    break;
        case XML_smConfetti: aHatch.Style = drawing::HatchStyle_SINGLE; aHatch.Distance = 200; aHatch.Angle = 600;  break;
        case XML_lgConfetti: aHatch.Style = drawing::HatchStyle_SINGLE; aHatch.Distance = 100; aHatch.Angle = 600;  break;
        case XML_horzBrick:  aHatch.Style = drawing::HatchStyle_DOUBLE; aHatch.Distance = 300; aHatch.Angle = 0;    break;
        case XML_diagBrick:  aHatch.Style = drawing::HatchStyle_DOUBLE; aHatch.Distance = 300; aHatch.Angle = 450;  break;
        case XML_solidDmnd:  aHatch.Style = drawing::HatchStyle_DOUBLE; aHatch.Distance = 100; aHatch.Angle = 450;  break;
        case XML_openDmnd:   aHatch.Style = drawing::HatchStyle_DOUBLE; aHatch.Distance = 100; aHatch.Angle = 450;  break;
        case XML_dotDmnd:    aHatch.Style = drawing::HatchStyle_DOUBLE; aHatch.Distance = 300; aHatch.Angle = 450;  break;
        case XML_plaid:      aHatch.Style = drawing::HatchStyle_TRIPLE; aHatch.Distance = 200; aHatch.Angle = 900;  break;
        case XML_sphere:     aHatch.Style = drawing::HatchStyle_TRIPLE; aHatch.Distance = 100; aHatch.Angle = 0;    break;
        case XML_weave:      aHatch.Style = drawing::HatchStyle_DOUBLE; aHatch.Distance = 150; aHatch.Angle = 450;  break;
        case XML_divot:      aHatch.Style = drawing::HatchStyle_TRIPLE; aHatch.Distance = 400; aHatch.Angle = 450;  break;
        case XML_shingle:    aHatch.Style = drawing::HatchStyle_SINGLE; aHatch.Distance = 200; aHatch.Angle = 1350; break;
        case XML_wave:       aHatch.Style = drawing::HatchStyle_SINGLE; aHatch.Distance = 100; aHatch.Angle = 0;    break;
        case XML_trellis:    aHatch.Style = drawing::HatchStyle_DOUBLE; aHatch.Distance = 75;  aHatch.Angle = 450;  break;
        case XML_zigZag:     aHatch.Style = drawing::HatchStyle_SINGLE; aHatch.Distance = 75;  aHatch.Angle = 0;    break;
    }

    return aHatch;
}

void FillProperties::pushToPropMap( ShapePropertyMap& rPropMap,
        const GraphicHelper& rGraphicHelper, sal_Int32 nShapeRotation, sal_Int32 nPhClr,
        bool bFlipH, bool bFlipV ) const
{
    if( moFillType.has() )
    {
        FillStyle eFillStyle = FillStyle_NONE;
        OSL_ASSERT((moFillType.get() & sal_Int32(0xFFFF0000))==0);
        switch( moFillType.get() )
        {
            case XML_noFill:
                eFillStyle = FillStyle_NONE;
            break;

            case XML_solidFill:
                if( maFillColor.isUsed() )
                {
                    rPropMap.setProperty( SHAPEPROP_FillColor, maFillColor.getColor( rGraphicHelper, nPhClr ) );
                    if( maFillColor.hasTransparency() )
                        rPropMap.setProperty( SHAPEPROP_FillTransparency, maFillColor.getTransparency() );
                    eFillStyle = FillStyle_SOLID;
                }
            break;

            case XML_gradFill:
                // do not create gradient struct if property is not supported...
                if( rPropMap.supportsProperty( SHAPEPROP_FillGradient ) )
                {
                    sal_Int32 nEndTrans     = 0;
                    sal_Int32 nStartTrans   = 0;
                    awt::Gradient aGradient;
                    aGradient.Angle = 900;
                    aGradient.StartIntensity = 100;
                    aGradient.EndIntensity = 100;

                    // Old code, values in aGradient overwritten in many cases by newer code below
                    if( maGradientProps.maGradientStops.size() > 1 )
                    {
                        aGradient.StartColor = maGradientProps.maGradientStops.begin()->second.getColor( rGraphicHelper, nPhClr );
                        aGradient.EndColor = maGradientProps.maGradientStops.rbegin()->second.getColor( rGraphicHelper, nPhClr );
                        if( maGradientProps.maGradientStops.rbegin()->second.hasTransparency() )
                            nEndTrans = maGradientProps.maGradientStops.rbegin()->second.getTransparency()*255/100;
                        if( maGradientProps.maGradientStops.begin()->second.hasTransparency() )
                            nStartTrans = maGradientProps.maGradientStops.begin()->second.getTransparency()*255/100;
                    }

                    // Adjust for flips
                    if ( bFlipH )
                        nShapeRotation = 180*60000 - nShapeRotation;
                    if ( bFlipV )
                        nShapeRotation = -nShapeRotation;

                    // "rotate with shape" not set, or set to false -> do not rotate
                    if ( !maGradientProps.moRotateWithShape.get( false ) )
                        nShapeRotation = 0;

                    sal_Int32 nDmlAngle = 0;
                    if( maGradientProps.moGradientPath.has() )
                    {
                        aGradient.Style = (maGradientProps.moGradientPath.get() == XML_circle) ? awt::GradientStyle_ELLIPTICAL : awt::GradientStyle_RECT;
                        // position of gradient center (limited to [30%;70%], otherwise gradient is too hidden)
                        IntegerRectangle2D aFillToRect = maGradientProps.moFillToRect.get( IntegerRectangle2D( 0, 0, MAX_PERCENT, MAX_PERCENT ) );
                        sal_Int32 nCenterX = (MAX_PERCENT + aFillToRect.X1 - aFillToRect.X2) / 2;
                        aGradient.XOffset = getLimitedValue< sal_Int16, sal_Int32 >( nCenterX / PER_PERCENT, 30, 70 );
                        sal_Int32 nCenterY = (MAX_PERCENT + aFillToRect.Y1 - aFillToRect.Y2) / 2;
                        aGradient.YOffset = getLimitedValue< sal_Int16, sal_Int32 >( nCenterY / PER_PERCENT, 30, 70 );
                        ::std::swap( aGradient.StartColor, aGradient.EndColor );
                        ::std::swap( nStartTrans, nEndTrans );
                        nDmlAngle = nShapeRotation;
                    }
                    else
                    {
                        // A copy of the gradient stops for local modification
                        GradientFillProperties::GradientStopMap aGradientStops(maGradientProps.maGradientStops);

                        // Add a fake gradient stop at 0% and 100% if necessary, so that the gradient always starts
                        // at 0% and ends at 100%, to make following logic clearer (?).
                        if( aGradientStops.find(0.0) == aGradientStops.end() )
                        {
                            // temp variable required
                            Color aFirstColor(aGradientStops.begin()->second);
                            aGradientStops[0.0] = aFirstColor;
                        }

                        if( aGradientStops.find(1.0) == aGradientStops.end() )
                        {
                            // ditto
                            Color aLastColor(aGradientStops.rbegin()->second);
                            aGradientStops[1.0] = aLastColor;
                        }

                        // Check if the gradient is symmetric, which we will emulate with an "axial" gradient.
                        bool bSymmetric(true);
                        {
                            GradientFillProperties::GradientStopMap::const_iterator aItA( aGradientStops.begin() );
                            GradientFillProperties::GradientStopMap::const_iterator aItZ( boost::prior( aGradientStops.end() ) );
                            while( bSymmetric && aItA->first < aItZ->first )
                            {
                                if( aItA->second.getColor( rGraphicHelper, nPhClr ) != aItZ->second.getColor( rGraphicHelper, nPhClr ) ||
                                    aItA->second.getTransparency() != aItZ->second.getTransparency() )
                                    bSymmetric = false;
                                else
                                {
                                    aItA++;
                                    aItZ = boost::prior(aItZ);
                                }
                            }
                            // Don't be fooled if the middlemost stop isn't at 0.5.
                            if( bSymmetric && aItA == aItZ && aItA->first != 0.5 )
                                bSymmetric = false;

                            // If symmetric, do the rest of the logic for just a half.
                            if( bSymmetric )
                            {
                                // aItZ already points to the colour for the middle, but insert a fake stop at the
                                // exact middle if necessary.
                                if( aItA->first != aItZ->first )
                                {
                                    Color aMiddleColor = aItZ->second;
                                    aGradientStops[0.5] = aMiddleColor;
                                }
                                // Drop the rest of the stops
                                while( aGradientStops.rbegin()->first > 0.5 )
                                    aGradientStops.erase( aGradientStops.rbegin()->first );
                            }
                        }

                        SAL_INFO("oox.drawingml.gradient", "symmetric: " << (bSymmetric ? "YES" : "NO") <<
                                 ", number of stops: " << aGradientStops.size());
                        for (GradientFillProperties::GradientStopMap::iterator p(aGradientStops.begin());
                             p != aGradientStops.end();
                             p++)
                            SAL_INFO("oox.drawingml.gradient", "  " << std::distance(aGradientStops.begin(), p) << ": " <<
                                     p->first << ": " <<
                                     std::hex << p->second.getColor( rGraphicHelper, nPhClr ) << std::dec <<
                                     "@" << (100-p->second.getTransparency()) << "%");

                        // Now estimate the simple LO style gradient (only two stops, at n% and 100%, where n ==
                        // the "border") that best emulates the gradient between begin() and prior(end()).

                        // First look for the largest segment in the gradient.
                        GradientFillProperties::GradientStopMap::iterator aIt(aGradientStops.begin());
                        double nWidestWidth = -1;
                        GradientFillProperties::GradientStopMap::iterator aWidestSegmentStart;
                        aIt++;
                        while( aIt != aGradientStops.end() )
                        {
                            if( aIt->first - boost::prior(aIt)->first > nWidestWidth )
                            {
                                nWidestWidth = aIt->first - boost::prior(aIt)->first;
                                aWidestSegmentStart = boost::prior(aIt);
                            }
                            aIt++;
                        }
                        assert( nWidestWidth > 0 );

                        double nBorder = 0;
                        bool bSwap(false);

                        // Do we have just two segments, and either one is of uniform colour, or three or more
                        // segments, and the widest one is the first or last one, and is it of uniform colour? If
                        // so, deduce the border from it, and drop that segment.
                        if( aGradientStops.size() == 3 &&
                            aGradientStops.begin()->second.getColor( rGraphicHelper, nPhClr ) == boost::next(aGradientStops.begin())->second.getColor( rGraphicHelper, nPhClr ) &&
                            aGradientStops.begin()->second.getTransparency() == boost::next(aGradientStops.begin())->second.getTransparency( ) )
                        {
                            // Two segments, first is uniformly coloured
                            SAL_INFO("oox.drawingml.gradient", "two segments, first is uniformly coloured");
                            nBorder = boost::next(aGradientStops.begin())->first - aGradientStops.begin()->first;
                            aGradientStops.erase(aGradientStops.begin());
                            aWidestSegmentStart = aGradientStops.begin();
                        }
                        else if( !bSymmetric &&
                                 aGradientStops.size() == 3 &&
                                 boost::next(aGradientStops.begin())->second.getColor( rGraphicHelper, nPhClr ) == boost::prior(aGradientStops.end())->second.getColor( rGraphicHelper, nPhClr ) &&
                                 boost::next(aGradientStops.begin())->second.getTransparency() == boost::prior(aGradientStops.end())->second.getTransparency( ) )
                        {
                            // Two segments, second is uniformly coloured
                            SAL_INFO("oox.drawingml.gradient", "two segments, second is uniformly coloured");
                            nBorder = boost::prior(aGradientStops.end())->first - boost::next(aGradientStops.begin())->first;
                            aGradientStops.erase(boost::next(aGradientStops.begin()));
                            aWidestSegmentStart = aGradientStops.begin();
                            bSwap = true;
                            nShapeRotation = 180*60000 - nShapeRotation;
                        }
                        else if( !bSymmetric &&
                                 aGradientStops.size() >= 4 &&
                                 aWidestSegmentStart->second.getColor( rGraphicHelper, nPhClr ) == boost::next(aWidestSegmentStart)->second.getColor( rGraphicHelper, nPhClr ) &&
                                 aWidestSegmentStart->second.getTransparency() == boost::next(aWidestSegmentStart)->second.getTransparency() &&
                                 ( aWidestSegmentStart == aGradientStops.begin() ||
                                   boost::next(aWidestSegmentStart) == boost::prior( aGradientStops.end() ) ) )
                        {
                            // Not symmetric, three or more segments, the widest is first or last and is uniformly coloured
                            SAL_INFO("oox.drawingml.gradient", "first or last segment is widest and is uniformly coloured");
                            nBorder = boost::next(aWidestSegmentStart)->first - aWidestSegmentStart->first;

                            // If it's the last segment that is uniformly coloured, rotate the gradient 180
                            // degrees and swap start and end colours
                            if( boost::next(aWidestSegmentStart) == boost::prior( aGradientStops.end() ) )
                            {
                                bSwap = true;
                                nShapeRotation = 180*60000 - nShapeRotation;
                            }

                            aGradientStops.erase( aWidestSegmentStart );

                            // Look for which is widest now
                            aIt = boost::next(aGradientStops.begin());
                            nWidestWidth = -1;
                            while( aIt != aGradientStops.end() )
                            {
                                if( aIt->first - boost::prior(aIt)->first > nWidestWidth )
                                {
                                    nWidestWidth = aIt->first - boost::prior(aIt)->first;
                                    aWidestSegmentStart = boost::prior(aIt);
                                }
                                aIt++;
                            }
                        }
                        SAL_INFO("oox.drawingml.gradient", "widest segment start: " << aWidestSegmentStart->first << ", border: " << nBorder);
                        assert( (!bSymmetric && !bSwap) || !(bSymmetric && bSwap) );

                        // Now we have a potential border and a largest segment. Use those.

                        aGradient.Style = bSymmetric ? awt::GradientStyle_AXIAL : awt::GradientStyle_LINEAR;
                        nDmlAngle = maGradientProps.moShadeAngle.get( 0 ) - nShapeRotation;
                        // convert DrawingML angle (in 1/60000 degrees) to API angle (in 1/10 degrees)
                        aGradient.Angle = static_cast< sal_Int16 >( (4500 - (nDmlAngle / (PER_DEGREE / 10))) % 3600 );
                        Color aStartColor, aEndColor;
                        if( bSymmetric )
                        {
                            aStartColor = boost::next(aWidestSegmentStart)->second;
                            aEndColor = aWidestSegmentStart->second;
                            nBorder *= 2;
                        }
                        else if( bSwap )
                        {
                            aStartColor = boost::next(aWidestSegmentStart)->second;
                            aEndColor = aWidestSegmentStart->second;
                        }
                        else
                        {
                            aStartColor = aWidestSegmentStart->second;
                            aEndColor = boost::next(aWidestSegmentStart)->second;
                        }

                        SAL_INFO("oox.drawingml.gradient", "start color: " << std::hex << aStartColor.getColor( rGraphicHelper, nPhClr ) << std::dec <<
                                 "@" << (100-aStartColor.getTransparency()) << "%"
                                 ", end color: " << std::hex << aEndColor.getColor( rGraphicHelper, nPhClr ) << std::dec <<
                                 "@" << (100-aEndColor.getTransparency()) << "%");

                        aGradient.StartColor = aStartColor.getColor( rGraphicHelper, nPhClr );
                        aGradient.EndColor = aEndColor.getColor( rGraphicHelper, nPhClr );

                        if( aStartColor.hasTransparency() )
                            nStartTrans = aStartColor.getTransparency()*255/100;
                        if( aEndColor.hasTransparency() )
                            nEndTrans = aEndColor.getTransparency()*255/100;

                        aGradient.Border = 100*nBorder;
                    }

                    // push gradient or named gradient to property map<commit_msg>use rtl::math::round here to get the same number on 32/64bit platforms<commit_after>/* -*- 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 <iterator>
#include <boost/utility.hpp>

#include "oox/drawingml/fillproperties.hxx"

#include <com/sun/star/lang/XMultiServiceFactory.hpp>
#include <com/sun/star/beans/XPropertySet.hpp>
#include <com/sun/star/awt/Gradient.hpp>
#include <com/sun/star/text/GraphicCrop.hpp>
#include <com/sun/star/awt/Size.hpp>
#include <com/sun/star/drawing/BitmapMode.hpp>
#include <com/sun/star/drawing/ColorMode.hpp>
#include <com/sun/star/drawing/FillStyle.hpp>
#include <com/sun/star/drawing/Hatch.hpp>
#include <com/sun/star/drawing/RectanglePoint.hpp>
#include <com/sun/star/graphic/XGraphicTransformer.hpp>
#include "oox/helper/graphichelper.hxx"
#include "oox/drawingml/drawingmltypes.hxx"
#include "oox/drawingml/shapepropertymap.hxx"
#include "oox/token/tokens.hxx"
#include <rtl/math.hxx>

using namespace ::com::sun::star;
using namespace ::com::sun::star::drawing;
using namespace ::com::sun::star::graphic;

using ::com::sun::star::uno::Reference;
using ::com::sun::star::uno::Exception;
using ::com::sun::star::uno::UNO_QUERY;
using ::com::sun::star::uno::UNO_QUERY_THROW;
using ::com::sun::star::geometry::IntegerRectangle2D;

namespace oox {
namespace drawingml {

namespace {

Reference< XGraphic > lclCheckAndApplyDuotoneTransform( const BlipFillProperties& aBlipProps, Reference< XGraphic > xGraphic,
                                                        const GraphicHelper& rGraphicHelper, const sal_Int32 nPhClr )
{
    if( aBlipProps.maDuotoneColors[0].isUsed() && aBlipProps.maDuotoneColors[1].isUsed() )
    {
        sal_Int32 nColor1 = aBlipProps.maDuotoneColors[0].getColor( rGraphicHelper, nPhClr );
        sal_Int32 nColor2 = aBlipProps.maDuotoneColors[1].getColor( rGraphicHelper, nPhClr );
        try
        {
            Reference< XGraphicTransformer > xTransformer( aBlipProps.mxGraphic, UNO_QUERY_THROW );
            xGraphic = xTransformer->applyDuotone( xGraphic, nColor1, nColor2 );
        }
        catch( Exception& )
        {
        }
    }
    return xGraphic;
}

Reference< XGraphic > lclCheckAndApplyChangeColorTransform( const BlipFillProperties &aBlipProps, Reference< XGraphic > xGraphic,
                                                            const GraphicHelper& rGraphicHelper, const sal_Int32 nPhClr )
{
    if( aBlipProps.maColorChangeFrom.isUsed() && aBlipProps.maColorChangeTo.isUsed() )
    {
        sal_Int32 nFromColor = aBlipProps.maColorChangeFrom.getColor( rGraphicHelper, nPhClr );
        sal_Int32 nToColor = aBlipProps.maColorChangeTo.getColor( rGraphicHelper, nPhClr );
        if ( (nFromColor != nToColor) || aBlipProps.maColorChangeTo.hasTransparency() ) try
        {
            sal_Int16 nToTransparence = aBlipProps.maColorChangeTo.getTransparency();
            sal_Int8 nToAlpha = static_cast< sal_Int8 >( (100 - nToTransparence) * 2.55 );
            Reference< XGraphicTransformer > xTransformer( aBlipProps.mxGraphic, UNO_QUERY_THROW );
            xGraphic = xTransformer->colorChange( xGraphic, nFromColor, 9, nToColor, nToAlpha );
        }
        catch( Exception& )
        {
        }
    }
    return xGraphic;
}

Reference< XGraphic > applyBrightnessContrast( Reference< XGraphic > xGraphic, sal_Int32 brightness, sal_Int32 contrast )
{
    try
        {
            Reference< XGraphicTransformer > xTransformer( xGraphic, UNO_QUERY_THROW );
            xGraphic = xTransformer->applyBrightnessContrast( xGraphic, brightness, contrast, true );
        }
        catch( Exception& )
        {
        }
    return xGraphic;
}

BitmapMode lclGetBitmapMode( sal_Int32 nToken )
{
    OSL_ASSERT((nToken & sal_Int32(0xFFFF0000))==0);
    switch( nToken )
    {
        case XML_tile:      return BitmapMode_REPEAT;
        case XML_stretch:   return BitmapMode_STRETCH;
    }
    return BitmapMode_NO_REPEAT;
}

RectanglePoint lclGetRectanglePoint( sal_Int32 nToken )
{
    OSL_ASSERT((nToken & sal_Int32(0xFFFF0000))==0);
    switch( nToken )
    {
        case XML_tl:    return RectanglePoint_LEFT_TOP;
        case XML_t:     return RectanglePoint_MIDDLE_TOP;
        case XML_tr:    return RectanglePoint_RIGHT_TOP;
        case XML_l:     return RectanglePoint_LEFT_MIDDLE;
        case XML_ctr:   return RectanglePoint_MIDDLE_MIDDLE;
        case XML_r:     return RectanglePoint_RIGHT_MIDDLE;
        case XML_bl:    return RectanglePoint_LEFT_BOTTOM;
        case XML_b:     return RectanglePoint_MIDDLE_BOTTOM;
        case XML_br:    return RectanglePoint_RIGHT_BOTTOM;
    }
    return RectanglePoint_LEFT_TOP;
}

const awt::Size lclGetOriginalSize( const GraphicHelper& rGraphicHelper, const Reference< XGraphic >& rxGraphic )
{
    awt::Size aSizeHmm( 0, 0 );
    try
    {
        Reference< beans::XPropertySet > xGraphicPropertySet( rxGraphic, UNO_QUERY_THROW );
        if( xGraphicPropertySet->getPropertyValue( "Size100thMM" ) >>= aSizeHmm )
        {
            if( !aSizeHmm.Width && !aSizeHmm.Height )
            {   // MAPMODE_PIXEL USED :-(
                awt::Size aSourceSizePixel( 0, 0 );
                if( xGraphicPropertySet->getPropertyValue( "SizePixel" ) >>= aSourceSizePixel )
                    aSizeHmm = rGraphicHelper.convertScreenPixelToHmm( aSourceSizePixel );
            }
        }
    }
    catch( Exception& )
    {
    }
    return aSizeHmm;
}

} // namespace

void GradientFillProperties::assignUsed( const GradientFillProperties& rSourceProps )
{
    if( !rSourceProps.maGradientStops.empty() )
        maGradientStops = rSourceProps.maGradientStops;
    moFillToRect.assignIfUsed( rSourceProps.moFillToRect );
    moTileRect.assignIfUsed( rSourceProps.moTileRect );
    moGradientPath.assignIfUsed( rSourceProps.moGradientPath );
    moShadeAngle.assignIfUsed( rSourceProps.moShadeAngle );
    moShadeFlip.assignIfUsed( rSourceProps.moShadeFlip );
    moShadeScaled.assignIfUsed( rSourceProps.moShadeScaled );
    moRotateWithShape.assignIfUsed( rSourceProps.moRotateWithShape );
}

void PatternFillProperties::assignUsed( const PatternFillProperties& rSourceProps )
{
    maPattFgColor.assignIfUsed( rSourceProps.maPattFgColor );
    maPattBgColor.assignIfUsed( rSourceProps.maPattBgColor );
    moPattPreset.assignIfUsed( rSourceProps.moPattPreset );
}

void BlipFillProperties::assignUsed( const BlipFillProperties& rSourceProps )
{
    if( rSourceProps.mxGraphic.is() )
        mxGraphic = rSourceProps.mxGraphic;
    moBitmapMode.assignIfUsed( rSourceProps.moBitmapMode );
    moFillRect.assignIfUsed( rSourceProps.moFillRect );
    moTileOffsetX.assignIfUsed( rSourceProps.moTileOffsetX );
    moTileOffsetY.assignIfUsed( rSourceProps.moTileOffsetY );
    moTileScaleX.assignIfUsed( rSourceProps.moTileScaleX );
    moTileScaleY.assignIfUsed( rSourceProps.moTileScaleY );
    moTileAlign.assignIfUsed( rSourceProps.moTileAlign );
    moTileFlip.assignIfUsed( rSourceProps.moTileFlip );
    moRotateWithShape.assignIfUsed( rSourceProps.moRotateWithShape );
    moColorEffect.assignIfUsed( rSourceProps.moColorEffect );
    moBrightness.assignIfUsed( rSourceProps.moBrightness );
    moContrast.assignIfUsed( rSourceProps.moContrast );
    maColorChangeFrom.assignIfUsed( rSourceProps.maColorChangeFrom );
    maColorChangeTo.assignIfUsed( rSourceProps.maColorChangeTo );
    maDuotoneColors[0].assignIfUsed( rSourceProps.maDuotoneColors[0] );
    maDuotoneColors[1].assignIfUsed( rSourceProps.maDuotoneColors[1] );
    maEffect.assignUsed( rSourceProps.maEffect );
}

void FillProperties::assignUsed( const FillProperties& rSourceProps )
{
    moFillType.assignIfUsed( rSourceProps.moFillType );
    maFillColor.assignIfUsed( rSourceProps.maFillColor );
    maGradientProps.assignUsed( rSourceProps.maGradientProps );
    maPatternProps.assignUsed( rSourceProps.maPatternProps );
    maBlipProps.assignUsed( rSourceProps.maBlipProps );
}

Color FillProperties::getBestSolidColor() const
{
    Color aSolidColor;
    if( moFillType.has() ) switch( moFillType.get() )
    {
        case XML_solidFill:
            aSolidColor = maFillColor;
        break;
        case XML_gradFill:
            if( !maGradientProps.maGradientStops.empty() )
                aSolidColor = maGradientProps.maGradientStops.begin()->second;
        break;
        case XML_pattFill:
            aSolidColor = maPatternProps.maPattBgColor.isUsed() ? maPatternProps.maPattBgColor : maPatternProps.maPattFgColor;
        break;
    }
    return aSolidColor;
}

/// Maps the hatch token to drawing::Hatch.
static drawing::Hatch createHatch( sal_Int32 nHatchToken, sal_Int32 nColor )
{
    drawing::Hatch aHatch;
    aHatch.Color = nColor;

    // best-effort mapping; we do not support all the styles in core
    switch ( nHatchToken )
    {
        case XML_pct5:       aHatch.Style = drawing::HatchStyle_SINGLE; aHatch.Distance = 250; aHatch.Angle = 450;  break;
        case XML_pct10:      aHatch.Style = drawing::HatchStyle_SINGLE; aHatch.Distance = 200; aHatch.Angle = 450;  break;
        case XML_pct20:      aHatch.Style = drawing::HatchStyle_SINGLE; aHatch.Distance = 150; aHatch.Angle = 450;  break;
        case XML_pct25:      aHatch.Style = drawing::HatchStyle_DOUBLE; aHatch.Distance = 200; aHatch.Angle = 450;  break;
        case XML_pct30:      aHatch.Style = drawing::HatchStyle_DOUBLE; aHatch.Distance = 175; aHatch.Angle = 450;  break;
        case XML_pct40:      aHatch.Style = drawing::HatchStyle_DOUBLE; aHatch.Distance = 150; aHatch.Angle = 450;  break;
        case XML_pct50:      aHatch.Style = drawing::HatchStyle_DOUBLE; aHatch.Distance = 125; aHatch.Angle = 450;  break;
        case XML_pct60:      aHatch.Style = drawing::HatchStyle_TRIPLE; aHatch.Distance = 150; aHatch.Angle = 450;  break;
        case XML_pct70:      aHatch.Style = drawing::HatchStyle_TRIPLE; aHatch.Distance = 125; aHatch.Angle = 450;  break;
        case XML_pct75:      aHatch.Style = drawing::HatchStyle_TRIPLE; aHatch.Distance = 100; aHatch.Angle = 450;  break;
        case XML_pct80:      aHatch.Style = drawing::HatchStyle_TRIPLE; aHatch.Distance = 75;  aHatch.Angle = 450;  break;
        case XML_pct90:      aHatch.Style = drawing::HatchStyle_TRIPLE; aHatch.Distance = 50;  aHatch.Angle = 450;  break;
        case XML_horz:       aHatch.Style = drawing::HatchStyle_SINGLE; aHatch.Distance = 100; aHatch.Angle = 0;    break;
        case XML_vert:       aHatch.Style = drawing::HatchStyle_SINGLE; aHatch.Distance = 100; aHatch.Angle = 900;  break;
        case XML_ltHorz:     aHatch.Style = drawing::HatchStyle_SINGLE; aHatch.Distance = 50;  aHatch.Angle = 0;    break;
        case XML_ltVert:     aHatch.Style = drawing::HatchStyle_SINGLE; aHatch.Distance = 50;  aHatch.Angle = 900;  break;
        case XML_dkHorz:     aHatch.Style = drawing::HatchStyle_SINGLE; aHatch.Distance = 25;  aHatch.Angle = 0;    break;
        case XML_dkVert:     aHatch.Style = drawing::HatchStyle_SINGLE; aHatch.Distance = 25;  aHatch.Angle = 900;  break;
        case XML_narHorz:    aHatch.Style = drawing::HatchStyle_SINGLE; aHatch.Distance = 50;  aHatch.Angle = 0;    break;
        case XML_narVert:    aHatch.Style = drawing::HatchStyle_SINGLE; aHatch.Distance = 50;  aHatch.Angle = 900;  break;
        case XML_dashHorz:   aHatch.Style = drawing::HatchStyle_SINGLE; aHatch.Distance = 150; aHatch.Angle = 0;    break;
        case XML_dashVert:   aHatch.Style = drawing::HatchStyle_SINGLE; aHatch.Distance = 150; aHatch.Angle = 900;  break;
        case XML_cross:      aHatch.Style = drawing::HatchStyle_DOUBLE; aHatch.Distance = 100; aHatch.Angle = 0;    break;
        case XML_dnDiag:     aHatch.Style = drawing::HatchStyle_SINGLE; aHatch.Distance = 100; aHatch.Angle = 1350; break;
        case XML_upDiag:     aHatch.Style = drawing::HatchStyle_SINGLE; aHatch.Distance = 100; aHatch.Angle = 450;  break;
        case XML_ltDnDiag:   aHatch.Style = drawing::HatchStyle_SINGLE; aHatch.Distance = 50;  aHatch.Angle = 1350; break;
        case XML_ltUpDiag:   aHatch.Style = drawing::HatchStyle_SINGLE; aHatch.Distance = 50;  aHatch.Angle = 450;  break;
        case XML_dkDnDiag:   aHatch.Style = drawing::HatchStyle_SINGLE; aHatch.Distance = 50;  aHatch.Angle = 1350; break;
        case XML_dkUpDiag:   aHatch.Style = drawing::HatchStyle_SINGLE; aHatch.Distance = 50;  aHatch.Angle = 450;  break;
        case XML_wdDnDiag:   aHatch.Style = drawing::HatchStyle_SINGLE; aHatch.Distance = 100; aHatch.Angle = 1350; break;
        case XML_wdUpDiag:   aHatch.Style = drawing::HatchStyle_SINGLE; aHatch.Distance = 100; aHatch.Angle = 450;  break;
        case XML_dashDnDiag: aHatch.Style = drawing::HatchStyle_SINGLE; aHatch.Distance = 150; aHatch.Angle = 1350; break;
        case XML_dashUpDiag: aHatch.Style = drawing::HatchStyle_SINGLE; aHatch.Distance = 150; aHatch.Angle = 450;  break;
        case XML_diagCross:  aHatch.Style = drawing::HatchStyle_DOUBLE; aHatch.Distance = 100; aHatch.Angle = 450;  break;
        case XML_smCheck:    aHatch.Style = drawing::HatchStyle_DOUBLE; aHatch.Distance = 50;  aHatch.Angle = 450;  break;
        case XML_lgCheck:    aHatch.Style = drawing::HatchStyle_DOUBLE; aHatch.Distance = 100; aHatch.Angle = 450;  break;
        case XML_smGrid:     aHatch.Style = drawing::HatchStyle_DOUBLE; aHatch.Distance = 50;  aHatch.Angle = 0;    break;
        case XML_lgGrid:     aHatch.Style = drawing::HatchStyle_DOUBLE; aHatch.Distance = 100; aHatch.Angle = 0;    break;
        case XML_dotGrid:    aHatch.Style = drawing::HatchStyle_DOUBLE; aHatch.Distance = 400; aHatch.Angle = 0;    break;
        case XML_smConfetti: aHatch.Style = drawing::HatchStyle_SINGLE; aHatch.Distance = 200; aHatch.Angle = 600;  break;
        case XML_lgConfetti: aHatch.Style = drawing::HatchStyle_SINGLE; aHatch.Distance = 100; aHatch.Angle = 600;  break;
        case XML_horzBrick:  aHatch.Style = drawing::HatchStyle_DOUBLE; aHatch.Distance = 300; aHatch.Angle = 0;    break;
        case XML_diagBrick:  aHatch.Style = drawing::HatchStyle_DOUBLE; aHatch.Distance = 300; aHatch.Angle = 450;  break;
        case XML_solidDmnd:  aHatch.Style = drawing::HatchStyle_DOUBLE; aHatch.Distance = 100; aHatch.Angle = 450;  break;
        case XML_openDmnd:   aHatch.Style = drawing::HatchStyle_DOUBLE; aHatch.Distance = 100; aHatch.Angle = 450;  break;
        case XML_dotDmnd:    aHatch.Style = drawing::HatchStyle_DOUBLE; aHatch.Distance = 300; aHatch.Angle = 450;  break;
        case XML_plaid:      aHatch.Style = drawing::HatchStyle_TRIPLE; aHatch.Distance = 200; aHatch.Angle = 900;  break;
        case XML_sphere:     aHatch.Style = drawing::HatchStyle_TRIPLE; aHatch.Distance = 100; aHatch.Angle = 0;    break;
        case XML_weave:      aHatch.Style = drawing::HatchStyle_DOUBLE; aHatch.Distance = 150; aHatch.Angle = 450;  break;
        case XML_divot:      aHatch.Style = drawing::HatchStyle_TRIPLE; aHatch.Distance = 400; aHatch.Angle = 450;  break;
        case XML_shingle:    aHatch.Style = drawing::HatchStyle_SINGLE; aHatch.Distance = 200; aHatch.Angle = 1350; break;
        case XML_wave:       aHatch.Style = drawing::HatchStyle_SINGLE; aHatch.Distance = 100; aHatch.Angle = 0;    break;
        case XML_trellis:    aHatch.Style = drawing::HatchStyle_DOUBLE; aHatch.Distance = 75;  aHatch.Angle = 450;  break;
        case XML_zigZag:     aHatch.Style = drawing::HatchStyle_SINGLE; aHatch.Distance = 75;  aHatch.Angle = 0;    break;
    }

    return aHatch;
}

void FillProperties::pushToPropMap( ShapePropertyMap& rPropMap,
        const GraphicHelper& rGraphicHelper, sal_Int32 nShapeRotation, sal_Int32 nPhClr,
        bool bFlipH, bool bFlipV ) const
{
    if( moFillType.has() )
    {
        FillStyle eFillStyle = FillStyle_NONE;
        OSL_ASSERT((moFillType.get() & sal_Int32(0xFFFF0000))==0);
        switch( moFillType.get() )
        {
            case XML_noFill:
                eFillStyle = FillStyle_NONE;
            break;

            case XML_solidFill:
                if( maFillColor.isUsed() )
                {
                    rPropMap.setProperty( SHAPEPROP_FillColor, maFillColor.getColor( rGraphicHelper, nPhClr ) );
                    if( maFillColor.hasTransparency() )
                        rPropMap.setProperty( SHAPEPROP_FillTransparency, maFillColor.getTransparency() );
                    eFillStyle = FillStyle_SOLID;
                }
            break;

            case XML_gradFill:
                // do not create gradient struct if property is not supported...
                if( rPropMap.supportsProperty( SHAPEPROP_FillGradient ) )
                {
                    sal_Int32 nEndTrans     = 0;
                    sal_Int32 nStartTrans   = 0;
                    awt::Gradient aGradient;
                    aGradient.Angle = 900;
                    aGradient.StartIntensity = 100;
                    aGradient.EndIntensity = 100;

                    // Old code, values in aGradient overwritten in many cases by newer code below
                    if( maGradientProps.maGradientStops.size() > 1 )
                    {
                        aGradient.StartColor = maGradientProps.maGradientStops.begin()->second.getColor( rGraphicHelper, nPhClr );
                        aGradient.EndColor = maGradientProps.maGradientStops.rbegin()->second.getColor( rGraphicHelper, nPhClr );
                        if( maGradientProps.maGradientStops.rbegin()->second.hasTransparency() )
                            nEndTrans = maGradientProps.maGradientStops.rbegin()->second.getTransparency()*255/100;
                        if( maGradientProps.maGradientStops.begin()->second.hasTransparency() )
                            nStartTrans = maGradientProps.maGradientStops.begin()->second.getTransparency()*255/100;
                    }

                    // Adjust for flips
                    if ( bFlipH )
                        nShapeRotation = 180*60000 - nShapeRotation;
                    if ( bFlipV )
                        nShapeRotation = -nShapeRotation;

                    // "rotate with shape" not set, or set to false -> do not rotate
                    if ( !maGradientProps.moRotateWithShape.get( false ) )
                        nShapeRotation = 0;

                    sal_Int32 nDmlAngle = 0;
                    if( maGradientProps.moGradientPath.has() )
                    {
                        aGradient.Style = (maGradientProps.moGradientPath.get() == XML_circle) ? awt::GradientStyle_ELLIPTICAL : awt::GradientStyle_RECT;
                        // position of gradient center (limited to [30%;70%], otherwise gradient is too hidden)
                        IntegerRectangle2D aFillToRect = maGradientProps.moFillToRect.get( IntegerRectangle2D( 0, 0, MAX_PERCENT, MAX_PERCENT ) );
                        sal_Int32 nCenterX = (MAX_PERCENT + aFillToRect.X1 - aFillToRect.X2) / 2;
                        aGradient.XOffset = getLimitedValue< sal_Int16, sal_Int32 >( nCenterX / PER_PERCENT, 30, 70 );
                        sal_Int32 nCenterY = (MAX_PERCENT + aFillToRect.Y1 - aFillToRect.Y2) / 2;
                        aGradient.YOffset = getLimitedValue< sal_Int16, sal_Int32 >( nCenterY / PER_PERCENT, 30, 70 );
                        ::std::swap( aGradient.StartColor, aGradient.EndColor );
                        ::std::swap( nStartTrans, nEndTrans );
                        nDmlAngle = nShapeRotation;
                    }
                    else
                    {
                        // A copy of the gradient stops for local modification
                        GradientFillProperties::GradientStopMap aGradientStops(maGradientProps.maGradientStops);

                        // Add a fake gradient stop at 0% and 100% if necessary, so that the gradient always starts
                        // at 0% and ends at 100%, to make following logic clearer (?).
                        if( aGradientStops.find(0.0) == aGradientStops.end() )
                        {
                            // temp variable required
                            Color aFirstColor(aGradientStops.begin()->second);
                            aGradientStops[0.0] = aFirstColor;
                        }

                        if( aGradientStops.find(1.0) == aGradientStops.end() )
                        {
                            // ditto
                            Color aLastColor(aGradientStops.rbegin()->second);
                            aGradientStops[1.0] = aLastColor;
                        }

                        // Check if the gradient is symmetric, which we will emulate with an "axial" gradient.
                        bool bSymmetric(true);
                        {
                            GradientFillProperties::GradientStopMap::const_iterator aItA( aGradientStops.begin() );
                            GradientFillProperties::GradientStopMap::const_iterator aItZ( boost::prior( aGradientStops.end() ) );
                            while( bSymmetric && aItA->first < aItZ->first )
                            {
                                if( aItA->second.getColor( rGraphicHelper, nPhClr ) != aItZ->second.getColor( rGraphicHelper, nPhClr ) ||
                                    aItA->second.getTransparency() != aItZ->second.getTransparency() )
                                    bSymmetric = false;
                                else
                                {
                                    aItA++;
                                    aItZ = boost::prior(aItZ);
                                }
                            }
                            // Don't be fooled if the middlemost stop isn't at 0.5.
                            if( bSymmetric && aItA == aItZ && aItA->first != 0.5 )
                                bSymmetric = false;

                            // If symmetric, do the rest of the logic for just a half.
                            if( bSymmetric )
                            {
                                // aItZ already points to the colour for the middle, but insert a fake stop at the
                                // exact middle if necessary.
                                if( aItA->first != aItZ->first )
                                {
                                    Color aMiddleColor = aItZ->second;
                                    aGradientStops[0.5] = aMiddleColor;
                                }
                                // Drop the rest of the stops
                                while( aGradientStops.rbegin()->first > 0.5 )
                                    aGradientStops.erase( aGradientStops.rbegin()->first );
                            }
                        }

                        SAL_INFO("oox.drawingml.gradient", "symmetric: " << (bSymmetric ? "YES" : "NO") <<
                                 ", number of stops: " << aGradientStops.size());
                        for (GradientFillProperties::GradientStopMap::iterator p(aGradientStops.begin());
                             p != aGradientStops.end();
                             p++)
                            SAL_INFO("oox.drawingml.gradient", "  " << std::distance(aGradientStops.begin(), p) << ": " <<
                                     p->first << ": " <<
                                     std::hex << p->second.getColor( rGraphicHelper, nPhClr ) << std::dec <<
                                     "@" << (100-p->second.getTransparency()) << "%");

                        // Now estimate the simple LO style gradient (only two stops, at n% and 100%, where n ==
                        // the "border") that best emulates the gradient between begin() and prior(end()).

                        // First look for the largest segment in the gradient.
                        GradientFillProperties::GradientStopMap::iterator aIt(aGradientStops.begin());
                        double nWidestWidth = -1;
                        GradientFillProperties::GradientStopMap::iterator aWidestSegmentStart;
                        aIt++;
                        while( aIt != aGradientStops.end() )
                        {
                            if( aIt->first - boost::prior(aIt)->first > nWidestWidth )
                            {
                                nWidestWidth = aIt->first - boost::prior(aIt)->first;
                                aWidestSegmentStart = boost::prior(aIt);
                            }
                            aIt++;
                        }
                        assert( nWidestWidth > 0 );

                        double nBorder = 0;
                        bool bSwap(false);

                        // Do we have just two segments, and either one is of uniform colour, or three or more
                        // segments, and the widest one is the first or last one, and is it of uniform colour? If
                        // so, deduce the border from it, and drop that segment.
                        if( aGradientStops.size() == 3 &&
                            aGradientStops.begin()->second.getColor( rGraphicHelper, nPhClr ) == boost::next(aGradientStops.begin())->second.getColor( rGraphicHelper, nPhClr ) &&
                            aGradientStops.begin()->second.getTransparency() == boost::next(aGradientStops.begin())->second.getTransparency( ) )
                        {
                            // Two segments, first is uniformly coloured
                            SAL_INFO("oox.drawingml.gradient", "two segments, first is uniformly coloured");
                            nBorder = boost::next(aGradientStops.begin())->first - aGradientStops.begin()->first;
                            aGradientStops.erase(aGradientStops.begin());
                            aWidestSegmentStart = aGradientStops.begin();
                        }
                        else if( !bSymmetric &&
                                 aGradientStops.size() == 3 &&
                                 boost::next(aGradientStops.begin())->second.getColor( rGraphicHelper, nPhClr ) == boost::prior(aGradientStops.end())->second.getColor( rGraphicHelper, nPhClr ) &&
                                 boost::next(aGradientStops.begin())->second.getTransparency() == boost::prior(aGradientStops.end())->second.getTransparency( ) )
                        {
                            // Two segments, second is uniformly coloured
                            SAL_INFO("oox.drawingml.gradient", "two segments, second is uniformly coloured");
                            nBorder = boost::prior(aGradientStops.end())->first - boost::next(aGradientStops.begin())->first;
                            aGradientStops.erase(boost::next(aGradientStops.begin()));
                            aWidestSegmentStart = aGradientStops.begin();
                            bSwap = true;
                            nShapeRotation = 180*60000 - nShapeRotation;
                        }
                        else if( !bSymmetric &&
                                 aGradientStops.size() >= 4 &&
                                 aWidestSegmentStart->second.getColor( rGraphicHelper, nPhClr ) == boost::next(aWidestSegmentStart)->second.getColor( rGraphicHelper, nPhClr ) &&
                                 aWidestSegmentStart->second.getTransparency() == boost::next(aWidestSegmentStart)->second.getTransparency() &&
                                 ( aWidestSegmentStart == aGradientStops.begin() ||
                                   boost::next(aWidestSegmentStart) == boost::prior( aGradientStops.end() ) ) )
                        {
                            // Not symmetric, three or more segments, the widest is first or last and is uniformly coloured
                            SAL_INFO("oox.drawingml.gradient", "first or last segment is widest and is uniformly coloured");
                            nBorder = boost::next(aWidestSegmentStart)->first - aWidestSegmentStart->first;

                            // If it's the last segment that is uniformly coloured, rotate the gradient 180
                            // degrees and swap start and end colours
                            if( boost::next(aWidestSegmentStart) == boost::prior( aGradientStops.end() ) )
                            {
                                bSwap = true;
                                nShapeRotation = 180*60000 - nShapeRotation;
                            }

                            aGradientStops.erase( aWidestSegmentStart );

                            // Look for which is widest now
                            aIt = boost::next(aGradientStops.begin());
                            nWidestWidth = -1;
                            while( aIt != aGradientStops.end() )
                            {
                                if( aIt->first - boost::prior(aIt)->first > nWidestWidth )
                                {
                                    nWidestWidth = aIt->first - boost::prior(aIt)->first;
                                    aWidestSegmentStart = boost::prior(aIt);
                                }
                                aIt++;
                            }
                        }
                        SAL_INFO("oox.drawingml.gradient", "widest segment start: " << aWidestSegmentStart->first << ", border: " << nBorder);
                        assert( (!bSymmetric && !bSwap) || !(bSymmetric && bSwap) );

                        // Now we have a potential border and a largest segment. Use those.

                        aGradient.Style = bSymmetric ? awt::GradientStyle_AXIAL : awt::GradientStyle_LINEAR;
                        nDmlAngle = maGradientProps.moShadeAngle.get( 0 ) - nShapeRotation;
                        // convert DrawingML angle (in 1/60000 degrees) to API angle (in 1/10 degrees)
                        aGradient.Angle = static_cast< sal_Int16 >( (4500 - (nDmlAngle / (PER_DEGREE / 10))) % 3600 );
                        Color aStartColor, aEndColor;
                        if( bSymmetric )
                        {
                            aStartColor = boost::next(aWidestSegmentStart)->second;
                            aEndColor = aWidestSegmentStart->second;
                            nBorder *= 2;
                        }
                        else if( bSwap )
                        {
                            aStartColor = boost::next(aWidestSegmentStart)->second;
                            aEndColor = aWidestSegmentStart->second;
                        }
                        else
                        {
                            aStartColor = aWidestSegmentStart->second;
                            aEndColor = boost::next(aWidestSegmentStart)->second;
                        }

                        SAL_INFO("oox.drawingml.gradient", "start color: " << std::hex << aStartColor.getColor( rGraphicHelper, nPhClr ) << std::dec <<
                                 "@" << (100-aStartColor.getTransparency()) << "%"
                                 ", end color: " << std::hex << aEndColor.getColor( rGraphicHelper, nPhClr ) << std::dec <<
                                 "@" << (100-aEndColor.getTransparency()) << "%");

                        aGradient.StartColor = aStartColor.getColor( rGraphicHelper, nPhClr );
                        aGradient.EndColor = aEndColor.getColor( rGraphicHelper, nPhClr );

                        if( aStartColor.hasTransparency() )
                            nStartTrans = aStartColor.getTransparency()*255/100;
                        if( aEndColor.hasTransparency() )
                            nEndTrans = aEndColor.getTransparency()*255/100;

                        aGradient.Border = rtl::math::round(100*nBorder);
                    }

                    // push gradient or named gradient to property map
{'fraction_non_alphanumeric': 0.09570931817833674, 'fraction_numerical': 0.017769335641304514, 'mean_word_length': 1.9369263991362247, 'pattern_counts': {'":': 4, '<': 174, '<?xml version=': 0, '>': 206, 'https://': 0, 'lorem ipsum': 0, 'www.': 2, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 78, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '1353900', 'n_tokens_mistral': 20238, 'n_tokens_neox': 18684, 'n_words': 3729}