Coverage Report

Created: 2026-10-14 09:33

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/libreoffice/xmloff/source/style/TransGradientStyle.cxx
Line
Count
Source
1
/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2
/*
3
 * This file is part of the LibreOffice project.
4
 *
5
 * This Source Code Form is subject to the terms of the Mozilla Public
6
 * License, v. 2.0. If a copy of the MPL was not distributed with this
7
 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
8
 *
9
 * This file incorporates work covered by the following license notice:
10
 *
11
 *   Licensed to the Apache Software Foundation (ASF) under one or more
12
 *   contributor license agreements. See the NOTICE file distributed
13
 *   with this work for additional information regarding copyright
14
 *   ownership. The ASF licenses this file to you under the Apache
15
 *   License, Version 2.0 (the "License"); you may not use this file
16
 *   except in compliance with the License. You may obtain a copy of
17
 *   the License at http://www.apache.org/licenses/LICENSE-2.0 .
18
 */
19
20
#include <TransGradientStyle.hxx>
21
22
#include <com/sun/star/awt/Gradient2.hpp>
23
24
#include <basegfx/utils/bgradient.hxx>
25
#include <comphelper/documentconstants.hxx>
26
#include <rtl/ustrbuf.hxx>
27
#include <rtl/ustring.hxx>
28
#include <sal/log.hxx>
29
#include <sax/tools/converter.hxx>
30
#include <tools/color.hxx>
31
#include <xmloff/xmlement.hxx>
32
#include <xmloff/xmlexp.hxx>
33
#include <xmloff/xmlimp.hxx>
34
#include <xmloff/xmlnamespace.hxx>
35
#include <xmloff/xmluconv.hxx>
36
#include <docmodel/uno/UnoGradientTools.hxx>
37
38
using namespace ::com::sun::star;
39
40
using namespace ::xmloff::token;
41
42
SvXMLEnumMapEntry<awt::GradientStyle> const pXML_GradientStyle_Enum[] =
43
{
44
    { XML_LINEAR,                       awt::GradientStyle_LINEAR },
45
    { XML_GRADIENTSTYLE_AXIAL,          awt::GradientStyle_AXIAL },
46
    { XML_GRADIENTSTYLE_RADIAL,         awt::GradientStyle_RADIAL },
47
    { XML_GRADIENTSTYLE_ELLIPSOID,      awt::GradientStyle_ELLIPTICAL },
48
    { XML_GRADIENTSTYLE_SQUARE,         awt::GradientStyle_SQUARE },
49
    { XML_GRADIENTSTYLE_RECTANGULAR,    awt::GradientStyle_RECT },
50
    { XML_TOKEN_INVALID,                awt::GradientStyle(0) }
51
};
52
53
// Import
54
55
XMLTransGradientStyleImport::XMLTransGradientStyleImport( SvXMLImport& rImp )
56
5
    : rImport(rImp)
57
5
{
58
5
}
59
60
void XMLTransGradientStyleImport::importXML(
61
    const uno::Reference< xml::sax::XFastAttributeList >& xAttrList,
62
    uno::Any& rValue,
63
    OUString& rStrName )
64
5
{
65
5
    OUString aDisplayName;
66
67
5
    awt::Gradient2 aGradient;
68
5
    aGradient.XOffset = 0;
69
5
    aGradient.YOffset = 0;
70
5
    aGradient.StartIntensity = 100;
71
5
    aGradient.EndIntensity = 100;
72
5
    aGradient.Angle = 0;
73
5
    aGradient.Border = 0;
74
75
5
    for (auto &aIter : sax_fastparser::castToFastAttributeList( xAttrList ))
76
0
    {
77
0
        sal_Int32 nTmpValue;
78
79
0
        switch( aIter.getToken() )
80
0
        {
81
0
        case XML_ELEMENT(DRAW, XML_NAME):
82
0
            {
83
0
                rStrName = aIter.toString();
84
0
            }
85
0
            break;
86
0
        case XML_ELEMENT(DRAW, XML_DISPLAY_NAME):
87
0
            {
88
0
                aDisplayName = aIter.toString();
89
0
            }
90
0
            break;
91
0
        case XML_ELEMENT(DRAW, XML_STYLE):
92
0
            {
93
0
                SvXMLUnitConverter::convertEnum( aGradient.Style, aIter.toView(), pXML_GradientStyle_Enum );
94
0
            }
95
0
            break;
96
0
        case XML_ELEMENT(DRAW, XML_CX):
97
0
            ::sax::Converter::convertPercent( nTmpValue, aIter.toView() );
98
0
            aGradient.XOffset = sal::static_int_cast< sal_Int16 >(nTmpValue);
99
0
            break;
100
0
        case XML_ELEMENT(DRAW, XML_CY):
101
0
            ::sax::Converter::convertPercent( nTmpValue, aIter.toView() );
102
0
            aGradient.YOffset = sal::static_int_cast< sal_Int16 >(nTmpValue);
103
0
            break;
104
0
        case XML_ELEMENT(DRAW, XML_START):
105
0
            {
106
0
                sal_Int32 aStartTransparency;
107
0
                ::sax::Converter::convertPercent( aStartTransparency, aIter.toView() );
108
109
0
                sal_uInt8 n = sal::static_int_cast< sal_uInt8 >(
110
0
                    ( (100 - aStartTransparency) * 255 ) / 100 );
111
112
0
                Color aColor( n, n, n );
113
0
                aGradient.StartColor = static_cast<sal_Int32>( aColor );
114
0
            }
115
0
            break;
116
0
        case XML_ELEMENT(DRAW, XML_END):
117
0
            {
118
0
                sal_Int32 aEndTransparency;
119
0
                ::sax::Converter::convertPercent( aEndTransparency, aIter.toView() );
120
121
0
                sal_uInt8 n = sal::static_int_cast< sal_uInt8 >(
122
0
                    ( (100 - aEndTransparency) * 255 ) / 100 );
123
124
0
                Color aColor( n, n, n );
125
0
                aGradient.EndColor = static_cast<sal_Int32>( aColor );
126
0
            }
127
0
            break;
128
0
        case XML_ELEMENT(DRAW, XML_GRADIENT_ANGLE):
129
0
            {
130
0
                auto const cmp12(rImport.GetODFVersion().compareTo(ODFVER_012_TEXT));
131
                // tdf#89475 try to detect borked OOo angles
132
0
                bool const bIsWrongOOo10thDegAngle(
133
0
                    (cmp12 < 0)
134
0
                    || (cmp12 == 0
135
0
                        && (rImport.isGeneratorVersionOlderThan(SvXMLImport::AOO_4x,
136
0
                                                                SvXMLImport::LO_7x)
137
                            // also for AOO 4.x, assume there won't ever be a 4.2
138
0
                            || rImport.getGeneratorVersion() == SvXMLImport::AOO_4x)));
139
0
                sal_Int16 nAngle;
140
0
                bool const bSuccess = ::sax::Converter::convert10thDegAngle(
141
0
                    nAngle, aIter.toView(), bIsWrongOOo10thDegAngle);
142
0
                if (bSuccess)
143
0
                { // limit to valid range [0..3600[
144
0
                    nAngle = nAngle % 3600;
145
0
                    if (nAngle < 0)
146
0
                        nAngle += 3600;
147
0
                    aGradient.Angle = nAngle;
148
0
                }
149
0
                SAL_INFO_IF(!bSuccess, "xmloff.style", "failed to import draw:angle");
150
0
            }
151
0
            break;
152
0
        case XML_ELEMENT(DRAW, XML_BORDER):
153
0
            ::sax::Converter::convertPercent( nTmpValue, aIter.toView() );
154
0
            aGradient.Border = sal::static_int_cast< sal_Int16 >(nTmpValue);
155
0
            break;
156
157
0
        default:
158
0
            XMLOFF_WARN_UNKNOWN("xmloff.style", aIter);
159
0
        }
160
0
    }
161
162
5
    rValue <<= aGradient;
163
164
5
    if( !aDisplayName.isEmpty() )
165
0
    {
166
0
        rImport.AddStyleDisplayName( XmlStyleFamily::SD_GRADIENT_ID, rStrName,
167
0
                                     aDisplayName );
168
0
        rStrName = aDisplayName;
169
0
    }
170
5
}
171
172
// Export
173
174
XMLTransGradientStyleExport::XMLTransGradientStyleExport( SvXMLExport& rExp )
175
49
    : rExport(rExp)
176
49
{
177
49
}
178
179
void XMLTransGradientStyleExport::exportXML(
180
    const OUString& rStrName,
181
    const uno::Any& rValue )
182
0
{
183
    // MCGR: We try to write the gradient so, that applications without multi-color gradient support
184
    // can render it as best as possible.
185
    // This is similar to XMLGradientStyleExport::exportXML(). For details see there.
186
0
    if( rStrName.isEmpty() )
187
0
        return;
188
0
    if (!rValue.has<css::awt::Gradient2>() && !rValue.has<css::awt::Gradient>())
189
0
        return;
190
191
0
    basegfx::BGradient aGradient = model::gradient::getFromAny(rValue);
192
193
0
    aGradient.tryToConvertToAxial();
194
195
0
    aGradient.tryToRecreateBorder(nullptr);
196
197
0
    OUString aStrValue;
198
0
    OUStringBuffer aOut;
199
200
    // Style
201
0
    if (!SvXMLUnitConverter::convertEnum(aOut, aGradient.GetGradientStyle(),
202
0
                                         pXML_GradientStyle_Enum))
203
0
        return;
204
205
    // Name
206
0
    bool bEncoded = false;
207
0
    rExport.AddAttribute( XML_NAMESPACE_DRAW, XML_NAME,
208
0
                          rExport.EncodeStyleName( rStrName,
209
0
                                                    &bEncoded ) );
210
0
    if( bEncoded )
211
0
        rExport.AddAttribute( XML_NAMESPACE_DRAW, XML_DISPLAY_NAME,
212
0
                                rStrName );
213
214
0
    aStrValue = aOut.makeStringAndClear();
215
0
    rExport.AddAttribute( XML_NAMESPACE_DRAW, XML_STYLE, aStrValue );
216
217
    // Center x/y
218
0
    if (awt::GradientStyle_LINEAR != aGradient.GetGradientStyle()
219
0
        && awt::GradientStyle_AXIAL != aGradient.GetGradientStyle())
220
0
    {
221
0
        ::sax::Converter::convertPercent(aOut, aGradient.GetXOffset());
222
0
        aStrValue = aOut.makeStringAndClear();
223
0
        rExport.AddAttribute( XML_NAMESPACE_DRAW, XML_CX, aStrValue );
224
225
0
        ::sax::Converter::convertPercent(aOut, aGradient.GetYOffset());
226
0
        aStrValue = aOut.makeStringAndClear();
227
0
        rExport.AddAttribute( XML_NAMESPACE_DRAW, XML_CY, aStrValue );
228
0
    }
229
230
    // LO uses a gray color as transparency. ODF uses opacity in range [0%,100%].
231
    // Default 100% opacity.
232
0
    double fOpacityStartPerc = 100.0;
233
0
    double fOpacityEndPerc = 100.0;
234
0
    if (!aGradient.GetColorStops().empty())
235
0
    {
236
0
        fOpacityStartPerc
237
0
            = (1.0 - aGradient.GetColorStops().front().getStopColor().getRed()) * 100.0;
238
0
        fOpacityEndPerc = (1.0 - aGradient.GetColorStops().back().getStopColor().getRed()) * 100.0;
239
0
    }
240
241
    // Opacity start
242
0
    ::sax::Converter::convertPercent(aOut, static_cast<sal_Int32>(std::round(fOpacityStartPerc)));
243
0
    aStrValue = aOut.makeStringAndClear();
244
0
    rExport.AddAttribute( XML_NAMESPACE_DRAW, XML_START, aStrValue );
245
246
    // Opacity end
247
0
    ::sax::Converter::convertPercent( aOut, static_cast<sal_Int32>(std::round(fOpacityEndPerc)));
248
0
    aStrValue = aOut.makeStringAndClear();
249
0
    rExport.AddAttribute( XML_NAMESPACE_DRAW, XML_END, aStrValue );
250
251
    // Angle
252
0
    if (awt::GradientStyle_RADIAL != aGradient.GetGradientStyle())
253
0
    {
254
        // true: wrong, but backward compatible with OOo/LO < 4.4
255
        // false: OFFICE-3774 tdf#89475 write valid ODF 1.2 angle; needs LO 4.4 to import
256
0
        bool bIsWrongOOo10thDegAngle(rExport.getSaneDefaultVersion() < SvtSaveOptions::ODFSVER_012
257
0
                                     || rExport.getSaneDefaultVersion()
258
0
                                            == SvtSaveOptions::ODFSVER_012_EXT_COMPAT);
259
0
        ::sax::Converter::convert10thDegAngle(aOut, aGradient.GetAngle().get(),
260
0
                                              bIsWrongOOo10thDegAngle);
261
0
        aStrValue = aOut.makeStringAndClear();
262
0
        rExport.AddAttribute(XML_NAMESPACE_DRAW, XML_GRADIENT_ANGLE, aStrValue);
263
0
    }
264
265
    // Border
266
0
    ::sax::Converter::convertPercent(aOut, aGradient.GetBorder());
267
0
    aStrValue = aOut.makeStringAndClear();
268
0
    rExport.AddAttribute( XML_NAMESPACE_DRAW, XML_BORDER, aStrValue );
269
270
    // ctor writes start tag. End-tag is written by destructor at block end.
271
0
    SvXMLElementExport rElem(rExport, XML_NAMESPACE_DRAW, XML_OPACITY, true, false);
272
273
    // Write child elements <loext:opacity-stop>
274
    // Do not export in standard ODF 1.3 or older.
275
0
    if ((rExport.getSaneDefaultVersion() & SvtSaveOptions::ODFSVER_EXTENDED) == 0)
276
0
        return;
277
0
    if (aGradient.GetColorStops().empty())
278
0
        return;
279
280
0
    double fPreviousOffset = 0.0;
281
0
    for (auto& aCandidate : aGradient.GetColorStops())
282
0
    {
283
        // Attribute svg:offset. Make sure offsets are increasing.
284
0
        double fOffset = std::clamp<double>(aCandidate.getStopOffset(), 0.0, 1.0);
285
0
        if (fOffset < fPreviousOffset)
286
0
            fOffset = fPreviousOffset;
287
0
        rExport.AddAttribute(XML_NAMESPACE_SVG, XML_OFFSET, OUString::number(fOffset));
288
0
        fPreviousOffset = fOffset;
289
290
        // Attribute svg:stop-opacity, data type zeroToOneDecimal
291
0
        double fOpacity = std::clamp<double>(1.0 - aCandidate.getStopColor().getRed(), 0.0, 1.0);
292
0
        rExport.AddAttribute(XML_NAMESPACE_SVG, XML_STOP_OPACITY, OUString::number(fOpacity));
293
294
        // write opacity stop element
295
0
        SvXMLElementExport aStopElement(rExport, XML_NAMESPACE_LO_EXT, XML_OPACITY_STOP, true, true);
296
0
    }
297
0
}
298
299
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */