Coverage Report

Created: 2026-10-14 09:33

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/libreoffice/oox/source/drawingml/chart/titleconverter.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 <drawingml/chart/titleconverter.hxx>
21
22
#include <com/sun/star/chart/ChartLegendExpansion.hpp>
23
#include <com/sun/star/chart2/FormattedString.hpp>
24
#include <com/sun/star/chart2/LegendPosition.hpp>
25
#include <com/sun/star/chart2/XDiagram.hpp>
26
#include <com/sun/star/chart2/XLegend.hpp>
27
#include <com/sun/star/chart2/XTitle.hpp>
28
#include <com/sun/star/chart2/XTitled.hpp>
29
#include <com/sun/star/chart2/XChartTypeContainer.hpp>
30
#include <com/sun/star/chart2/XCoordinateSystemContainer.hpp>
31
#include <com/sun/star/chart2/XDataSeriesContainer.hpp>
32
33
#include <comphelper/sequence.hxx>
34
#include <osl/diagnose.h>
35
#include <drawingml/textbody.hxx>
36
#include <drawingml/textparagraph.hxx>
37
#include <drawingml/chart/axismodel.hxx>
38
#include <drawingml/chart/datasourceconverter.hxx>
39
#include <drawingml/chart/titlemodel.hxx>
40
#include <oox/token/properties.hxx>
41
#include <oox/token/tokens.hxx>
42
#include <com/sun/star/chart2/RelativePosition.hpp>
43
#include <com/sun/star/drawing/Alignment.hpp>
44
45
#include <oox/drawingml/chart/modelbase.hxx>
46
namespace oox::drawingml::chart {
47
48
using namespace ::com::sun::star::chart2;
49
using namespace ::com::sun::star::chart2::data;
50
using namespace ::com::sun::star::drawing;
51
using namespace ::com::sun::star::uno;
52
53
54
TextConverter::TextConverter( const ConverterRoot& rParent, TextModel& rModel ) :
55
0
    ConverterBase< TextModel >( rParent, rModel )
56
0
{
57
0
}
58
59
TextConverter::~TextConverter()
60
0
{
61
0
}
62
63
Reference< XDataSequence > TextConverter::createDataSequence( const OUString& rRole )
64
0
{
65
0
    Reference< XDataSequence > xDataSeq;
66
0
    if( mrModel.mxDataSeq.is() )
67
0
    {
68
0
        DataSequenceConverter aDataSeqConv( *this, *mrModel.mxDataSeq );
69
0
        xDataSeq = aDataSeqConv.createDataSequence( rRole, DataSourceType::UNKNOWN );
70
0
    }
71
0
    return xDataSeq;
72
0
}
73
74
Sequence< Reference< XFormattedString > > TextConverter::createStringSequence(
75
        const OUString& rDefaultText, const ModelRef< TextBody >& rxTextProp, ObjectType eObjType )
76
0
{
77
0
    OSL_ENSURE( !mrModel.mxDataSeq || !mrModel.mxTextBody, "TextConverter::createStringSequence - linked string and rich text found" );
78
0
    ::std::vector< Reference< XFormattedString > > aStringVec;
79
0
    bool bTextFound = false;
80
0
    if( mrModel.mxTextBody.is() )
81
0
    {
82
        // rich-formatted text objects can be created, but currently Chart2 is not able to show them
83
0
        const TextParagraphVector& rTextParas = mrModel.mxTextBody->getParagraphs();
84
0
        for( TextParagraphVector::const_iterator aPIt = rTextParas.begin(), aPEnd = rTextParas.end(); aPIt != aPEnd; ++aPIt )
85
0
        {
86
0
            const TextParagraph& rTextPara = **aPIt;
87
0
            const TextCharacterProperties& rParaProps = rTextPara.getProperties().getTextCharacterProperties();
88
0
            for( TextRunVector::const_iterator aRIt = rTextPara.getRuns().begin(), aREnd = rTextPara.getRuns().end(); aRIt != aREnd; ++aRIt )
89
0
            {
90
0
                const TextRun& rTextRun = **aRIt;
91
0
                bool bAddNewLine = ((aRIt + 1 == aREnd) && (aPIt + 1 != aPEnd)) || rTextRun.isLineBreak();
92
0
                Reference< XFormattedString > xFmtStr = appendFormattedString( aStringVec, rTextRun.getText(), bAddNewLine );
93
0
                PropertySet aPropSet( xFmtStr );
94
0
                TextCharacterProperties aRunProps;
95
0
                if (rParaProps.mbHasEmptyParaProperties && rxTextProp.is() && rxTextProp->hasParagraphProperties())
96
0
                {
97
0
                    const TextParagraphVector rDefTextParas = rxTextProp->getParagraphs();
98
0
                    TextParagraphVector::const_iterator aDefPIt = rDefTextParas.begin();
99
0
                    const TextParagraph& rDefTextPara = **aDefPIt;
100
0
                    aRunProps = rDefTextPara.getProperties().getTextCharacterProperties();
101
0
                }
102
0
                else
103
0
                    aRunProps = rParaProps;
104
0
                aRunProps.assignUsed( rTextRun.getTextCharacterProperties() );
105
0
                getFormatter().convertTextFormatting( aPropSet, aRunProps, eObjType );
106
107
0
                bTextFound = true;
108
0
            }
109
0
        }
110
0
    }
111
0
    else if (rxTextProp.is() && !rxTextProp->getParagraphs().empty()) {
112
        // <c:txPr> or <cx:txPr> can contain <a:p>. Which seems odd, but handle
113
        // it here.
114
0
        const TextParagraphVector& rTextParas = rxTextProp->getParagraphs();
115
0
        for( TextParagraphVector::const_iterator aPIt = rTextParas.begin(), aPEnd = rTextParas.end(); aPIt != aPEnd; ++aPIt )
116
0
        {
117
0
            const TextParagraph& rTextPara = **aPIt;
118
0
            const TextCharacterProperties& rParaProps = rTextPara.getProperties().getTextCharacterProperties();
119
0
            for( TextRunVector::const_iterator aRIt = rTextPara.getRuns().begin(), aREnd = rTextPara.getRuns().end(); aRIt != aREnd; ++aRIt )
120
0
            {
121
0
                const TextRun& rTextRun = **aRIt;
122
0
                bool bAddNewLine = ((aRIt + 1 == aREnd) && (aPIt + 1 != aPEnd)) || rTextRun.isLineBreak();
123
0
                Reference< XFormattedString > xFmtStr = appendFormattedString( aStringVec, rTextRun.getText(), bAddNewLine );
124
0
                PropertySet aPropSet( xFmtStr );
125
0
                TextCharacterProperties aRunProps;
126
0
                if (rParaProps.mbHasEmptyParaProperties && rxTextProp->hasParagraphProperties())
127
0
                {
128
0
                    const TextParagraphVector rDefTextParas = rxTextProp->getParagraphs();
129
0
                    TextParagraphVector::const_iterator aDefPIt = rDefTextParas.begin();
130
0
                    const TextParagraph& rDefTextPara = **aDefPIt;
131
0
                    aRunProps = rDefTextPara.getProperties().getTextCharacterProperties();
132
0
                }
133
0
                else
134
0
                    aRunProps = rParaProps;
135
0
                aRunProps.assignUsed( rTextRun.getTextCharacterProperties() );
136
0
                getFormatter().convertTextFormatting( aPropSet, aRunProps, eObjType );
137
138
0
                bTextFound = true;
139
0
            }
140
0
        }
141
0
    }
142
143
0
    if (!bTextFound)
144
0
    {
145
0
        OUString aString;
146
        // try to create string from linked data
147
0
        if( mrModel.mxDataSeq.is() && !mrModel.mxDataSeq->maData.empty() )
148
0
            mrModel.mxDataSeq->maData.begin()->second >>= aString;
149
        // no linked string -> fall back to default string
150
0
        if( aString.isEmpty() )
151
0
            aString = rDefaultText;
152
153
        // create formatted string object
154
0
        if( !aString.isEmpty() )
155
0
        {
156
0
            Reference< XFormattedString > xFmtStr = appendFormattedString( aStringVec, aString, false );
157
0
            PropertySet aPropSet( xFmtStr );
158
0
            getFormatter().convertTextFormatting( aPropSet, rxTextProp, eObjType );
159
0
        }
160
0
    }
161
162
0
    return comphelper::containerToSequence( aStringVec );
163
0
}
164
165
Reference< XFormattedString > TextConverter::appendFormattedString(
166
        ::std::vector< Reference< XFormattedString > >& orStringVec, const OUString& rString, bool bAddNewLine ) const
167
0
{
168
0
    Reference< XFormattedString2 > xFmtStr;
169
0
    try
170
0
    {
171
0
        xFmtStr = FormattedString::create( ConverterRoot::getComponentContext() );
172
0
        xFmtStr->setString( bAddNewLine ? (rString + "\n") : rString );
173
0
        orStringVec.emplace_back(xFmtStr );
174
0
    }
175
0
    catch( Exception& )
176
0
    {
177
0
    }
178
0
    return xFmtStr;
179
0
}
180
181
TitleConverter::TitleConverter( const ConverterRoot& rParent, TitleModel& rModel ) :
182
0
    ConverterBase< TitleModel >( rParent, rModel )
183
0
{
184
0
}
185
186
TitleConverter::~TitleConverter()
187
0
{
188
0
}
189
190
void TitleConverter::convertFromModel( const Reference< XTitled >& rxTitled, const OUString& rAutoTitle, ObjectType eObjType, ChartType eCT, sal_Int32 nMainIdx, sal_Int32 nSubIdx )
191
0
{
192
0
    if( !rxTitled.is() )
193
0
        return;
194
195
    // create the formatted strings
196
0
    TextModel& rText = mrModel.mxText.getOrCreate();
197
0
    TextConverter aTextConv( *this, rText );
198
0
    Sequence< Reference< XFormattedString > > aStringSeq = aTextConv.createStringSequence( rAutoTitle, mrModel.mxTextProp, eObjType );
199
0
    if( !aStringSeq.hasElements() )
200
0
        return;
201
202
0
    try
203
0
    {
204
        // create the title object and set the string data
205
0
        Reference< XTitle > xTitle( createInstance( u"com.sun.star.chart2.Title"_ustr ), UNO_QUERY_THROW );
206
0
        xTitle->setText( aStringSeq );
207
0
        rxTitled->setTitleObject( xTitle );
208
209
        // frame formatting (text formatting already done in TextConverter::createStringSequence())
210
0
        PropertySet aPropSet( xTitle );
211
0
        getFormatter().convertFrameFormatting( aPropSet, mrModel.mxShapeProp, eObjType );
212
213
0
        if (eCT == ChartType::CX && mrModel.mxShapeProp.is()) {
214
0
            aPropSet.setProperty(PROP_HasExplicitSpPr, true);
215
0
        }
216
217
        // frame rotation
218
0
        OSL_ENSURE( !mrModel.mxTextProp || !rText.mxTextBody, "TitleConverter::convertFromModel - multiple text properties" );
219
0
        ModelRef< TextBody > xTextProp = mrModel.mxTextProp.is() ? mrModel.mxTextProp : rText.mxTextBody;
220
0
        ObjectFormatter::convertTextRotation( aPropSet, xTextProp, true, mrModel.mnDefaultRotation );
221
222
        // register the title and layout data for conversion of position
223
0
        registerTitleLayout( xTitle, mrModel.mxLayout, eObjType, nMainIdx, nSubIdx );
224
0
    }
225
0
    catch( Exception& )
226
0
    {
227
0
    }
228
0
}
229
230
LegendConverter::LegendConverter( const ConverterRoot& rParent, LegendModel& rModel ) :
231
0
    ConverterBase< LegendModel >( rParent, rModel )
232
0
{
233
0
}
234
235
LegendConverter::~LegendConverter()
236
0
{
237
0
}
238
239
void LegendConverter::convertFromModel( const Reference< XDiagram >& rxDiagram, ChartType eCT )
240
0
{
241
0
    if( !rxDiagram.is() )
242
0
        return;
243
244
0
    try
245
0
    {
246
0
        namespace cssc = css::chart;
247
0
        namespace cssc2 = css::chart2;
248
249
        // create the legend
250
0
        Reference< XLegend > xLegend( createInstance( u"com.sun.star.chart2.Legend"_ustr ), UNO_QUERY_THROW );
251
0
        rxDiagram->setLegend( xLegend );
252
0
        PropertySet aPropSet( xLegend );
253
0
        aPropSet.setProperty( PROP_Show, true );
254
255
        // legend formatting
256
0
        getFormatter().convertFormatting( aPropSet, mrModel.mxShapeProp, mrModel.mxTextProp, OBJECTTYPE_LEGEND );
257
258
        // predefined legend position and expansion
259
0
        cssc2::LegendPosition eLegendPos = cssc2::LegendPosition_LINE_END;
260
0
        cssc::ChartLegendExpansion eLegendExpand = cssc::ChartLegendExpansion_CUSTOM;
261
0
        RelativePosition eRelPos;
262
0
        bool bTopRight = false;
263
0
        switch( mrModel.mnPosition )
264
0
        {
265
0
            case XML_l:
266
0
                eLegendPos = cssc2::LegendPosition_LINE_START;
267
0
                eLegendExpand = cssc::ChartLegendExpansion_HIGH;
268
0
            break;
269
0
            case XML_r:
270
0
                eLegendPos = cssc2::LegendPosition_LINE_END;
271
0
                eLegendExpand = cssc::ChartLegendExpansion_HIGH;
272
0
            break;
273
0
            case XML_tr:    // top-right not supported
274
0
                eRelPos.Primary = 1;
275
0
                eRelPos.Secondary = 0;
276
0
                eRelPos.Anchor = Alignment_TOP_RIGHT;
277
0
                bTopRight = true;
278
0
            break;
279
0
            case XML_t:
280
0
                eLegendPos = cssc2::LegendPosition_PAGE_START;
281
0
                eLegendExpand = cssc::ChartLegendExpansion_WIDE;
282
0
            break;
283
0
            case XML_b:
284
0
                eLegendPos = cssc2::LegendPosition_PAGE_END;
285
0
                eLegendExpand = cssc::ChartLegendExpansion_WIDE;
286
0
            break;
287
0
        }
288
0
        bool bManualLayout = false;
289
        // manual positioning and size
290
0
        if( mrModel.mxLayout )
291
0
        {
292
0
            LayoutConverter aLayoutConv( *this, *mrModel.mxLayout );
293
            // manual size needs ChartLegendExpansion_CUSTOM
294
0
            if( aLayoutConv.convertFromModel( aPropSet ) )
295
0
            {
296
0
                eLegendExpand = cssc::ChartLegendExpansion_CUSTOM;
297
0
            }
298
0
            bManualLayout = !aLayoutConv.getAutoLayout();
299
0
        }
300
301
        // set position and expansion properties
302
0
        aPropSet.setProperty( PROP_AnchorPosition, eLegendPos );
303
0
        aPropSet.setProperty( PROP_Expansion, eLegendExpand );
304
305
0
        if (bTopRight && !bManualLayout)
306
0
            aPropSet.setProperty( PROP_RelativePosition, Any(eRelPos));
307
308
0
        aPropSet.setProperty(PROP_Overlay, mrModel.mbOverlay);
309
310
0
        if (mrModel.maLegendEntries.size() > 0)
311
0
            legendEntriesFormatting(rxDiagram);
312
313
0
        if (eCT == ChartType::CX && mrModel.mxShapeProp.is()) {
314
0
            aPropSet.setProperty(PROP_HasExplicitSpPr, true);
315
0
        }
316
0
    }
317
0
    catch( Exception& )
318
0
    {
319
0
    }
320
0
}
321
322
void LegendConverter::legendEntriesFormatting(const Reference<XDiagram>& rxDiagram)
323
0
{
324
0
    Reference<XCoordinateSystemContainer> xCooSysContainer(rxDiagram, UNO_QUERY_THROW);
325
0
    const Sequence<Reference<XCoordinateSystem>> xCooSysSequence(xCooSysContainer->getCoordinateSystems());
326
0
    if (!xCooSysSequence.hasElements())
327
0
        return;
328
329
0
    sal_Int32 nIndex = 0;
330
0
    for (const auto& rCooSys : xCooSysSequence)
331
0
    {
332
0
        PropertySet aCooSysProp(rCooSys);
333
0
        bool bSwapXAndY = aCooSysProp.getBoolProperty(PROP_SwapXAndYAxis);
334
335
0
        Reference<XChartTypeContainer> xChartTypeContainer(rCooSys, UNO_QUERY_THROW);
336
0
        const Sequence<Reference<XChartType>> xChartTypeSequence(xChartTypeContainer->getChartTypes());
337
0
        if (!xChartTypeSequence.hasElements())
338
0
            continue;
339
340
0
        for (const auto& rCT : xChartTypeSequence)
341
0
        {
342
0
            Reference<XDataSeriesContainer> xDSCont(rCT, UNO_QUERY);
343
0
            if (!xDSCont.is())
344
0
                continue;
345
346
0
            bool bIsPie
347
0
                = rCT->getChartType().equalsIgnoreAsciiCase("com.sun.star.chart2.PieChartType");
348
0
            if (bIsPie)
349
0
            {
350
0
                PropertySet xChartTypeProp(rCT);
351
0
                bIsPie = !xChartTypeProp.getBoolProperty(PROP_UseRings);
352
0
            }
353
0
            const Sequence<Reference<XDataSeries>> aDataSeriesSeq = xDSCont->getDataSeries();
354
0
            if (bSwapXAndY)
355
0
                nIndex += aDataSeriesSeq.getLength() - 1;
356
0
            for (const auto& rDataSeries : aDataSeriesSeq)
357
0
            {
358
0
                PropertySet aSeriesProp(rDataSeries);
359
0
                bool bVaryColorsByPoint = aSeriesProp.getBoolProperty(PROP_VaryColorsByPoint);
360
361
0
                if (bVaryColorsByPoint || bIsPie)
362
0
                {
363
0
                    Reference<XDataSource> xDSrc(rDataSeries, UNO_QUERY);
364
0
                    if (!xDSrc.is())
365
0
                        continue;
366
367
0
                    const Sequence<Reference<XLabeledDataSequence> > aDataSeqs = xDSrc->getDataSequences();
368
0
                    std::vector<sal_Int32> deletedLegendEntries;
369
0
                    sal_Int32 j = 0;
370
0
                    for (const auto& rDataSeq : aDataSeqs)
371
0
                    {
372
0
                        Reference<XDataSequence> xValues = rDataSeq->getValues();
373
0
                        if (!xValues.is())
374
0
                            continue;
375
376
0
                        sal_Int32 nDataSeqSize = xValues->getData().getLength();
377
0
                        for (sal_Int32 i = 0; i < nDataSeqSize; ++i)
378
0
                        {
379
0
                            for (const auto& rLegendEntry : mrModel.maLegendEntries)
380
0
                            {
381
0
                                if (nIndex == rLegendEntry->mnLegendEntryIdx && rLegendEntry->mbLabelDeleted)
382
0
                                {
383
0
                                    deletedLegendEntries.push_back(j + i);
384
0
                                    break;
385
0
                                }
386
0
                            }
387
0
                            nIndex++;
388
0
                        }
389
0
                        j += nDataSeqSize;
390
0
                    }
391
0
                    if (deletedLegendEntries.size() > 0)
392
0
                        aSeriesProp.setProperty(PROP_DeletedLegendEntries, comphelper::containerToSequence(deletedLegendEntries));
393
0
                }
394
0
                else
395
0
                {
396
0
                    for (const auto& rLegendEntry : mrModel.maLegendEntries)
397
0
                    {
398
0
                        if (nIndex == rLegendEntry->mnLegendEntryIdx)
399
0
                        {
400
0
                            aSeriesProp.setProperty(PROP_ShowLegendEntry, !rLegendEntry->mbLabelDeleted);
401
0
                            break;
402
0
                        }
403
0
                    }
404
0
                    bSwapXAndY ? nIndex-- : nIndex++;
405
0
                }
406
0
            }
407
0
            if (bSwapXAndY)
408
0
                nIndex += aDataSeriesSeq.getLength() + 1;
409
0
        }
410
0
    }
411
0
}
412
413
} // namespace oox::drawingml::chart
414
415
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */