Coverage Report

Created: 2026-07-10 11:04

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/libreoffice/editeng/source/uno/unonrule.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 <sal/config.h>
21
22
#include <com/sun/star/awt/FontDescriptor.hpp>
23
#include <com/sun/star/beans/PropertyValue.hpp>
24
#include <com/sun/star/lang/IllegalArgumentException.hpp>
25
#include <com/sun/star/lang/IndexOutOfBoundsException.hpp>
26
#include <com/sun/star/text/HoriOrientation.hpp>
27
#include <com/sun/star/awt/XBitmap.hpp>
28
#include <com/sun/star/graphic/XGraphic.hpp>
29
#include <cppuhelper/supportsservice.hxx>
30
#include <utility>
31
#include <vcl/font.hxx>
32
#include <vcl/svapp.hxx>
33
#include <vcl/graph.hxx>
34
#include <vcl/GraphicObject.hxx>
35
#include <vcl/GraphicLoader.hxx>
36
#include <tools/debug.hxx>
37
38
#include <editeng/brushitem.hxx>
39
#include <editeng/unoprnms.hxx>
40
#include <editeng/numitem.hxx>
41
#include <editeng/unofdesc.hxx>
42
#include <editeng/unonrule.hxx>
43
#include <editeng/editids.hrc>
44
#include <o3tl/enumarray.hxx>
45
#include <o3tl/temporary.hxx>
46
#include <memory>
47
48
using ::com::sun::star::util::XCloneable;
49
using ::com::sun::star::ucb::XAnyCompare;
50
51
52
using namespace ::com::sun::star;
53
using namespace ::com::sun::star::uno;
54
using namespace ::com::sun::star::lang;
55
using namespace ::com::sun::star::container;
56
57
const SvxAdjust aUnoToSvxAdjust[] =
58
{
59
    SvxAdjust::Left,
60
    SvxAdjust::Right,
61
    SvxAdjust::Center,
62
    SvxAdjust::Left,
63
    SvxAdjust::Left,
64
    SvxAdjust::Left,
65
    SvxAdjust::Block
66
};
67
68
const o3tl::enumarray<SvxAdjust, sal_Int16> aSvxToUnoAdjust
69
{
70
    text::HoriOrientation::LEFT,
71
    text::HoriOrientation::RIGHT,
72
    text::HoriOrientation::FULL,
73
    text::HoriOrientation::CENTER,
74
    text::HoriOrientation::FULL,
75
    text::HoriOrientation::LEFT,
76
    text::HoriOrientation::RIGHT,
77
    text::HoriOrientation::LEFT
78
};
79
80
static SvxAdjust ConvertUnoAdjust( unsigned short nAdjust )
81
339k
{
82
339k
    DBG_ASSERT( nAdjust <= 7, "Enum has changed! [CL]" );
83
339k
    return aUnoToSvxAdjust[nAdjust];
84
339k
}
85
86
static unsigned short ConvertUnoAdjust( SvxAdjust eAdjust )
87
722k
{
88
722k
    DBG_ASSERT( static_cast<int>(eAdjust) <= 6, "Enum has changed! [CL]" );
89
722k
    return aSvxToUnoAdjust[eAdjust];
90
722k
}
91
92
SvxUnoNumberingRules::SvxUnoNumberingRules(SvxNumRule aRule)
93
281k
: maRule(std::move( aRule ))
94
281k
{
95
281k
}
96
97
SvxUnoNumberingRules::~SvxUnoNumberingRules() noexcept
98
281k
{
99
281k
}
100
101
//XIndexReplace
102
void SAL_CALL SvxUnoNumberingRules::replaceByIndex( sal_Int32 Index, const uno::Any& Element )
103
351k
{
104
351k
    SolarMutexGuard aGuard;
105
106
351k
    if( Index < 0 || Index >= maRule.GetLevelCount() )
107
3.76k
        throw IndexOutOfBoundsException();
108
109
347k
    Sequence< beans::PropertyValue > aSeq;
110
111
347k
    if( !( Element >>= aSeq) )
112
0
        throw IllegalArgumentException();
113
347k
    setNumberingRuleByIndex( aSeq, Index );
114
347k
}
115
116
// XIndexAccess
117
sal_Int32 SAL_CALL SvxUnoNumberingRules::getCount()
118
101k
{
119
101k
    SolarMutexGuard aGuard;
120
121
101k
    return maRule.GetLevelCount();
122
101k
}
123
124
Any SAL_CALL SvxUnoNumberingRules::getByIndex( sal_Int32 Index )
125
722k
{
126
722k
    SolarMutexGuard aGuard;
127
128
722k
    if( Index < 0 || Index >= maRule.GetLevelCount() )
129
0
        throw IndexOutOfBoundsException();
130
131
722k
    return Any( getNumberingRuleByIndex(Index) );
132
722k
}
133
134
//XElementAccess
135
Type SAL_CALL SvxUnoNumberingRules::getElementType()
136
0
{
137
0
    return cppu::UnoType<Sequence< beans::PropertyValue >>::get();
138
0
}
139
140
sal_Bool SAL_CALL SvxUnoNumberingRules::hasElements()
141
0
{
142
0
    return true;
143
0
}
144
145
// XAnyCompare
146
sal_Int16 SAL_CALL SvxUnoNumberingRules::compare( const Any& rAny1, const Any& rAny2 )
147
21.5k
{
148
21.5k
    return SvxUnoNumberingRules::Compare( rAny1, rAny2 );
149
21.5k
}
150
151
// XCloneable
152
Reference< XCloneable > SAL_CALL SvxUnoNumberingRules::createClone(  )
153
0
{
154
0
    return new SvxUnoNumberingRules(maRule);
155
0
}
156
157
OUString SAL_CALL SvxUnoNumberingRules::getImplementationName(  )
158
0
{
159
0
    return u"SvxUnoNumberingRules"_ustr;
160
0
}
161
162
sal_Bool SAL_CALL SvxUnoNumberingRules::supportsService( const OUString& ServiceName )
163
0
{
164
0
    return cppu::supportsService(this, ServiceName);
165
0
}
166
167
Sequence< OUString > SAL_CALL SvxUnoNumberingRules::getSupportedServiceNames(  )
168
0
{
169
0
    return { u"com.sun.star.text.NumberingRules"_ustr };
170
0
}
171
172
Sequence<beans::PropertyValue> SvxUnoNumberingRules::getNumberingRuleByIndex(sal_Int32 nIndex) const
173
722k
{
174
    //  NumberingRule aRule;
175
722k
    const SvxNumberFormat& rFmt = maRule.GetLevel(static_cast<sal_uInt16>(nIndex));
176
722k
    sal_uInt16 nIdx = 0;
177
178
722k
    const int nProps = 15;
179
722k
    std::unique_ptr<beans::PropertyValue[]> pArray(new beans::PropertyValue[nProps]);
180
181
722k
    Any aVal;
182
722k
    {
183
722k
        aVal <<= static_cast<sal_uInt16>(rFmt.GetNumberingType());
184
722k
        pArray[nIdx++] = beans::PropertyValue(UNO_NAME_NRULE_NUMBERINGTYPE, -1, aVal, beans::PropertyState_DIRECT_VALUE);
185
722k
    }
186
187
722k
    {
188
722k
        SvxAdjust eAdj = rFmt.GetNumAdjust();
189
722k
        aVal <<= ConvertUnoAdjust(eAdj);
190
722k
        pArray[nIdx++] = beans::PropertyValue(UNO_NAME_NRULE_ADJUST, -1, aVal, beans::PropertyState_DIRECT_VALUE);
191
722k
    }
192
193
722k
    {
194
722k
        aVal <<= rFmt.GetPrefix();
195
722k
        pArray[nIdx++] = beans::PropertyValue(UNO_NAME_NRULE_PREFIX, -1, aVal, beans::PropertyState_DIRECT_VALUE);
196
722k
    }
197
198
722k
    {
199
722k
        aVal <<= rFmt.GetSuffix();
200
722k
        pArray[nIdx++] = beans::PropertyValue(UNO_NAME_NRULE_SUFFIX, -1, aVal, beans::PropertyState_DIRECT_VALUE);
201
722k
    }
202
203
722k
    if(SVX_NUM_CHAR_SPECIAL == rFmt.GetNumberingType())
204
721k
    {
205
721k
        sal_UCS4 nCode = rFmt.GetBulletChar();
206
721k
        OUString aStr( &nCode, 1 );
207
721k
        aVal <<= aStr;
208
721k
        pArray[nIdx++] = beans::PropertyValue(u"BulletChar"_ustr, -1, aVal, beans::PropertyState_DIRECT_VALUE);
209
721k
    }
210
211
722k
    if( rFmt.GetBulletFont() )
212
722k
    {
213
722k
        awt::FontDescriptor aDesc;
214
722k
        SvxUnoFontDescriptor::ConvertFromFont( *rFmt.GetBulletFont(), aDesc );
215
722k
        aVal <<= aDesc;
216
722k
        pArray[nIdx++] = beans::PropertyValue(UNO_NAME_NRULE_BULLET_FONT, -1, aVal, beans::PropertyState_DIRECT_VALUE);
217
722k
    }
218
219
722k
    {
220
722k
        const SvxBrushItem* pBrush = rFmt.GetBrush();
221
722k
        const Graphic* pGraphic = nullptr;
222
722k
        if (pBrush)
223
0
            pGraphic = pBrush->GetGraphic();
224
722k
        if (pGraphic)
225
0
        {
226
0
            uno::Reference<awt::XBitmap> xBitmap(pGraphic->GetXGraphic(), uno::UNO_QUERY);
227
0
            aVal <<= xBitmap;
228
229
0
            pArray[nIdx++] = beans::PropertyValue(u"GraphicBitmap"_ustr, -1, aVal, beans::PropertyState_DIRECT_VALUE);
230
0
        }
231
722k
    }
232
233
722k
    {
234
722k
        const Size aSize( rFmt.GetGraphicSize() );
235
722k
        const awt::Size aUnoSize( aSize.Width(), aSize.Height() );
236
722k
        aVal <<= aUnoSize;
237
722k
        pArray[nIdx++] = beans::PropertyValue(u"GraphicSize"_ustr, -1, aVal, beans::PropertyState_DIRECT_VALUE);
238
722k
    }
239
240
722k
    aVal <<= static_cast<sal_Int16>(rFmt.GetStart());
241
722k
    pArray[nIdx++] = beans::PropertyValue(UNO_NAME_NRULE_START_WITH, -1, aVal, beans::PropertyState_DIRECT_VALUE);
242
243
722k
    aVal <<= rFmt.GetAbsLSpace();
244
722k
    pArray[nIdx++] = beans::PropertyValue(UNO_NAME_NRULE_LEFT_MARGIN, -1, aVal, beans::PropertyState_DIRECT_VALUE);
245
246
722k
    aVal <<= rFmt.GetFirstLineOffset();
247
722k
    pArray[nIdx++] = beans::PropertyValue(UNO_NAME_NRULE_FIRST_LINE_OFFSET, -1, aVal, beans::PropertyState_DIRECT_VALUE);
248
249
722k
    pArray[nIdx++] = beans::PropertyValue(u"SymbolTextDistance"_ustr, -1, aVal, beans::PropertyState_DIRECT_VALUE);
250
251
722k
    aVal <<= rFmt.GetBulletColor();
252
722k
    pArray[nIdx++] = beans::PropertyValue(UNO_NAME_NRULE_BULLET_COLOR, -1, aVal, beans::PropertyState_DIRECT_VALUE);
253
254
722k
    aVal <<= static_cast<sal_Int16>(rFmt.GetBulletRelSize());
255
722k
    pArray[nIdx++] = beans::PropertyValue(UNO_NAME_NRULE_BULLET_RELSIZE, -1, aVal, beans::PropertyState_DIRECT_VALUE);
256
257
722k
    DBG_ASSERT( nIdx <= nProps, "FixMe: overflow in Array!!! [CL]" );
258
722k
    Sequence< beans::PropertyValue> aSeq(pArray.get(), nIdx);
259
260
722k
    return aSeq;
261
722k
}
262
263
void SvxUnoNumberingRules::setNumberingRuleByIndex(const Sequence<beans::PropertyValue >& rProperties, sal_Int32 nIndex)
264
347k
{
265
347k
    SvxNumberFormat aFmt(maRule.GetLevel( static_cast<sal_uInt16>(nIndex) ));
266
347k
    for(const beans::PropertyValue& rProp : rProperties)
267
3.41M
    {
268
3.41M
        const OUString& rPropName = rProp.Name;
269
3.41M
        const Any& aVal = rProp.Value;
270
271
3.41M
        if ( rPropName == UNO_NAME_NRULE_NUMBERINGTYPE )
272
257k
        {
273
257k
            sal_Int16 nSet = sal_Int16();
274
257k
            aVal >>= nSet;
275
276
            // There is no reason to limit numbering types.
277
257k
            if ( nSet>=0 )
278
257k
            {
279
257k
                aFmt.SetNumberingType(static_cast<SvxNumType>(nSet));
280
257k
                continue;
281
257k
            }
282
257k
        }
283
3.15M
        else if ( rPropName == UNO_NAME_NRULE_PREFIX )
284
239k
        {
285
239k
            OUString aPrefix;
286
239k
            if( aVal >>= aPrefix )
287
239k
            {
288
239k
                aFmt.SetPrefix(aPrefix);
289
239k
                continue;
290
239k
            }
291
239k
        }
292
2.91M
        else if ( rPropName == UNO_NAME_NRULE_SUFFIX )
293
239k
        {
294
239k
            OUString aSuffix;
295
239k
            if( aVal >>= aSuffix )
296
239k
            {
297
239k
                aFmt.SetSuffix(aSuffix);
298
239k
                continue;
299
239k
            }
300
239k
        }
301
2.67M
        else if ( rPropName == UNO_NAME_NRULE_BULLETID )
302
0
        {
303
0
            sal_Int16 nSet = sal_Int16();
304
0
            if( aVal >>= nSet )
305
0
            {
306
0
                if(nSet < 0x100)
307
0
                {
308
0
                    aFmt.SetBulletChar(nSet);
309
0
                    continue;
310
0
                }
311
0
            }
312
0
        }
313
2.67M
        else if ( rPropName == "BulletChar" )
314
247k
        {
315
247k
            OUString aStr;
316
247k
            if( aVal >>= aStr )
317
247k
            {
318
247k
                if(!aStr.isEmpty())
319
247k
                {
320
247k
                    aFmt.SetBulletChar(aStr.iterateCodePoints(&o3tl::temporary(sal_Int32(0))));
321
247k
                }
322
0
                else
323
0
                {
324
0
                    aFmt.SetBulletChar(0);
325
0
                }
326
247k
                continue;
327
247k
            }
328
247k
        }
329
2.42M
        else if ( rPropName == UNO_NAME_NRULE_ADJUST )
330
339k
        {
331
339k
            sal_Int16 nAdjust = sal_Int16();
332
339k
            if( aVal >>= nAdjust )
333
339k
            {
334
339k
                aFmt.SetNumAdjust(ConvertUnoAdjust( static_cast<unsigned short>(nAdjust) ));
335
339k
                continue;
336
339k
            }
337
339k
        }
338
2.09M
        else if ( rPropName == UNO_NAME_NRULE_BULLET_FONT )
339
246k
        {
340
246k
            awt::FontDescriptor aDesc;
341
246k
            if( aVal >>= aDesc )
342
246k
            {
343
246k
                vcl::Font aFont;
344
246k
                SvxUnoFontDescriptor::ConvertToFont( aDesc, aFont );
345
246k
                aFmt.SetBulletFont(&aFont);
346
246k
                continue;
347
246k
            }
348
246k
        }
349
1.84M
        else if ( rPropName == "GraphicURL" )
350
0
        {
351
0
            OUString aURL;
352
0
            if (aVal >>= aURL)
353
0
            {
354
0
                Graphic aGraphic = vcl::graphic::loadFromURL(aURL);
355
0
                if (!aGraphic.IsNone())
356
0
                {
357
0
                    SvxBrushItem aBrushItem(aGraphic, GPOS_AREA, SID_ATTR_BRUSH);
358
0
                    aFmt.SetGraphicBrush(&aBrushItem);
359
0
                }
360
0
                continue;
361
0
            }
362
0
        }
363
1.84M
        else if ( rPropName == "GraphicBitmap" )
364
0
        {
365
0
            uno::Reference<awt::XBitmap> xBitmap;
366
0
            if (aVal >>= xBitmap)
367
0
            {
368
0
                uno::Reference<graphic::XGraphic> xGraphic(xBitmap, uno::UNO_QUERY);
369
0
                Graphic aGraphic(xGraphic);
370
0
                SvxBrushItem aBrushItem(aGraphic, GPOS_AREA, SID_ATTR_BRUSH);
371
0
                aFmt.SetGraphicBrush( &aBrushItem );
372
0
                continue;
373
0
            }
374
0
        }
375
1.84M
        else if ( rPropName == "GraphicSize" )
376
192k
        {
377
192k
            awt::Size aUnoSize;
378
192k
            if( aVal >>= aUnoSize )
379
192k
            {
380
192k
                aFmt.SetGraphicSize( Size( aUnoSize.Width, aUnoSize.Height ) );
381
192k
                continue;
382
192k
            }
383
192k
        }
384
1.65M
        else if ( rPropName == UNO_NAME_NRULE_START_WITH )
385
192k
        {
386
192k
            sal_Int16 nStart = sal_Int16();
387
192k
            if( aVal >>= nStart )
388
192k
            {
389
192k
                aFmt.SetStart( nStart );
390
192k
                continue;
391
192k
            }
392
192k
        }
393
1.45M
        else if ( rPropName == UNO_NAME_NRULE_LEFT_MARGIN )
394
285k
        {
395
285k
            sal_Int32 nMargin = 0;
396
285k
            if( aVal >>= nMargin )
397
285k
            {
398
285k
                aFmt.SetAbsLSpace(nMargin);
399
285k
                continue;
400
285k
            }
401
285k
        }
402
1.17M
        else if ( rPropName == UNO_NAME_NRULE_FIRST_LINE_OFFSET )
403
232k
        {
404
232k
            sal_Int32 nMargin = 0;
405
232k
            if( aVal >>= nMargin )
406
232k
            {
407
232k
                aFmt.SetFirstLineOffset(nMargin);
408
232k
                continue;
409
232k
            }
410
232k
        }
411
940k
        else if ( rPropName == "SymbolTextDistance" )
412
194k
        {
413
194k
            sal_Int32 nTextDistance = 0;
414
194k
            if( aVal >>= nTextDistance )
415
194k
            {
416
194k
                aFmt.SetCharTextDistance(static_cast<sal_uInt16>(nTextDistance));
417
194k
                continue;
418
194k
            }
419
194k
        }
420
745k
        else if ( rPropName == UNO_NAME_NRULE_BULLET_COLOR )
421
255k
        {
422
255k
            Color aColor;
423
255k
            if( aVal >>= aColor )
424
255k
            {
425
255k
                aFmt.SetBulletColor( aColor );
426
255k
                continue;
427
255k
            }
428
255k
        }
429
490k
        else if ( rPropName == UNO_NAME_NRULE_BULLET_RELSIZE )
430
339k
        {
431
339k
            sal_Int16 nSize = sal_Int16();
432
339k
            if( aVal >>= nSize )
433
339k
            {
434
                // [AOO Bug 120650] the slide content corrupt when open in Aoo
435
                // [TDF# 126234] when MS Office document being imported, the value of the relative size
436
                // of the bullet could be as high as 400%
437
339k
                if ((nSize>400)||(nSize<=0))
438
0
                {
439
0
                    nSize = 100;
440
0
                }
441
442
339k
                aFmt.SetBulletRelSize( static_cast<short>(nSize) );
443
339k
                continue;
444
339k
            }
445
339k
        }
446
150k
        else
447
150k
        {
448
150k
            continue;
449
150k
        }
450
451
0
        throw IllegalArgumentException();
452
3.41M
    }
453
454
    // check that we always have a brush item for bitmap numbering
455
347k
    if( aFmt.GetNumberingType() == SVX_NUM_BITMAP )
456
0
    {
457
0
        if( nullptr == aFmt.GetBrush() )
458
0
        {
459
0
            GraphicObject aGrafObj;
460
0
            SvxBrushItem aBrushItem( aGrafObj, GPOS_AREA, SID_ATTR_BRUSH );
461
0
            aFmt.SetGraphicBrush( &aBrushItem );
462
0
        }
463
0
    }
464
347k
    maRule.SetLevel( static_cast<sal_uInt16>(nIndex), aFmt );
465
347k
}
466
467
const SvxNumRule& SvxGetNumRule( Reference< XIndexReplace > const & xRule )
468
143k
{
469
143k
    SvxUnoNumberingRules* pRule = dynamic_cast<SvxUnoNumberingRules*>( xRule.get() );
470
143k
    if( pRule == nullptr )
471
21.5k
        throw IllegalArgumentException();
472
473
122k
    return pRule->getNumRule();
474
143k
}
475
476
css::uno::Reference< css::container::XIndexReplace > SvxCreateNumRule(const SvxNumRule& rRule)
477
281k
{
478
281k
    return new SvxUnoNumberingRules( rRule );
479
281k
}
480
481
namespace {
482
483
class SvxUnoNumberingRulesCompare : public ::cppu::WeakImplHelper< XAnyCompare >
484
{
485
public:
486
    virtual sal_Int16 SAL_CALL compare( const Any& Any1, const Any& Any2 ) override;
487
};
488
489
}
490
491
sal_Int16 SAL_CALL SvxUnoNumberingRulesCompare::compare( const Any& Any1, const Any& Any2 )
492
7.10k
{
493
7.10k
    return SvxUnoNumberingRules::Compare( Any1, Any2 );
494
7.10k
}
495
496
sal_Int16 SvxUnoNumberingRules::Compare( const Any& Any1, const Any& Any2 )
497
28.6k
{
498
28.6k
    Reference< XIndexReplace > x1( Any1, UNO_QUERY ), x2( Any2, UNO_QUERY );
499
28.6k
    if( !x1 || !x2 )
500
0
        return -1;
501
502
28.6k
    if( x1.get() == x2.get() )
503
0
        return 0;
504
505
28.6k
    SvxUnoNumberingRules* pRule1 = dynamic_cast<SvxUnoNumberingRules*>( x1.get() );
506
28.6k
    if( !pRule1 )
507
0
        return -1;
508
28.6k
    SvxUnoNumberingRules* pRule2 = dynamic_cast<SvxUnoNumberingRules*>( x2.get() );
509
28.6k
    if( !pRule2 )
510
21.5k
        return -1;
511
512
7.17k
    const SvxNumRule& rRule1 = pRule1->getNumRule();
513
7.17k
    const SvxNumRule& rRule2 = pRule2->getNumRule();
514
515
7.17k
    const sal_uInt16 nLevelCount1 = rRule1.GetLevelCount();
516
7.17k
    const sal_uInt16 nLevelCount2 = rRule2.GetLevelCount();
517
518
7.17k
    if( nLevelCount1 == 0 || nLevelCount2 == 0 )
519
67
        return -1;
520
521
78.1k
    for( sal_uInt16 i = 0; (i < nLevelCount1) && (i < nLevelCount2); i++ )
522
71.0k
    {
523
71.0k
        if( rRule1.GetLevel(i) != rRule2.GetLevel(i) )
524
3
            return -1;
525
71.0k
    }
526
7.10k
    return  0;
527
7.10k
}
528
529
Reference< XAnyCompare > SvxCreateNumRuleCompare() noexcept
530
765
{
531
765
    return new SvxUnoNumberingRulesCompare;
532
765
}
533
534
css::uno::Reference< css::container::XIndexReplace > SvxCreateNumRule()
535
0
{
536
0
    SvxNumRule aTempRule( SvxNumRuleFlags::NONE, 10, false );
537
0
    return SvxCreateNumRule( aTempRule );
538
0
}
539
540
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */