Coverage Report

Created: 2026-07-10 11:04

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/libreoffice/vcl/source/outdev/polygon.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/types.h>
21
#include <basegfx/matrix/b2dhommatrix.hxx>
22
#include <tools/poly.hxx>
23
24
#include <vcl/rendercontext/AntialiasingFlags.hxx>
25
#include <vcl/metaact.hxx>
26
#include <vcl/virdev.hxx>
27
28
#include <CoordinateMapper.hxx>
29
#include <salgdi.hxx>
30
31
#include <cassert>
32
#include <memory>
33
34
639
#define OUTDEV_POLYPOLY_STACKBUF        32
35
36
void OutputDevice::DrawPolyPolygon( const tools::PolyPolygon& rPolyPoly )
37
514k
{
38
514k
    assert(!is_double_buffered_window());
39
40
514k
    if( mpMetaFile )
41
20.8k
        mpMetaFile->AddAction( new MetaPolyPolygonAction( rPolyPoly ) );
42
43
514k
    sal_uInt16 nPoly = rPolyPoly.Count();
44
45
514k
    if ( !IsDeviceOutputNecessary() || (!mbLineColor && !mbFillColor) || !nPoly || ImplIsRecordLayout() )
46
30.6k
        return;
47
48
    // we need a graphics
49
483k
    if ( !mpGraphics && !AcquireGraphics() )
50
0
        return;
51
483k
    assert(mpGraphics);
52
53
483k
    if ( mbInitClipRegion )
54
71
        InitClipRegion();
55
56
483k
    if ( mbOutputClipped )
57
73
        return;
58
59
483k
    if ( mbInitLineColor )
60
154
        InitLineColor();
61
62
483k
    if ( mbInitFillColor )
63
403
        InitFillColor();
64
65
    // use b2dpolygon drawing if possible
66
483k
    if (RasterOp::OverPaint == GetRasterOp() && (IsLineColor() || IsFillColor()))
67
482k
    {
68
482k
        const basegfx::B2DHomMatrix aTransform(mpMapper->GetDeviceTransformation());
69
482k
        basegfx::B2DPolyPolygon aB2DPolyPolygon(rPolyPoly.getB2DPolyPolygon());
70
71
        // ensure closed - may be asserted, will prevent buffering
72
482k
        if(!aB2DPolyPolygon.isClosed())
73
19.3k
        {
74
19.3k
            aB2DPolyPolygon.setClosed(true);
75
19.3k
        }
76
77
482k
        if (IsFillColor())
78
482k
        {
79
482k
            mpGraphics->DrawPolyPolygon(
80
482k
                aTransform,
81
482k
                aB2DPolyPolygon,
82
482k
                0.0,
83
482k
                *this);
84
482k
        }
85
86
482k
        bool bSuccess(true);
87
482k
        if (IsLineColor())
88
4.35k
        {
89
4.35k
            const bool bPixelSnapHairline(mnAntialiasing & AntialiasingFlags::PixelSnapHairline);
90
91
4.35k
            for(auto const& rPolygon : std::as_const(aB2DPolyPolygon))
92
16.0k
            {
93
16.0k
                bSuccess = mpGraphics->DrawPolyLine(
94
16.0k
                    aTransform,
95
16.0k
                    rPolygon,
96
16.0k
                    0.0,
97
16.0k
                    0.0, // tdf#124848 hairline
98
16.0k
                    nullptr, // MM01
99
16.0k
                    basegfx::B2DLineJoin::NONE,
100
16.0k
                    css::drawing::LineCap_BUTT,
101
16.0k
                    basegfx::deg2rad(15.0), // not used with B2DLineJoin::NONE, but the correct default
102
16.0k
                    bPixelSnapHairline,
103
16.0k
                    *this);
104
16.0k
                if (!bSuccess)
105
0
                    break;
106
16.0k
            }
107
4.35k
        }
108
109
482k
        if(bSuccess)
110
482k
            return;
111
482k
    }
112
113
926
    if ( nPoly == 1 )
114
297
    {
115
        // #100127# Map to DrawPolygon
116
297
        const tools::Polygon& aPoly = rPolyPoly.GetObject( 0 );
117
297
        if( aPoly.GetSize() >= 2 )
118
295
        {
119
295
            GDIMetaFile* pOldMF = mpMetaFile;
120
295
            mpMetaFile = nullptr;
121
122
295
            DrawPolygon( aPoly );
123
124
295
            mpMetaFile = pOldMF;
125
295
        }
126
297
    }
127
629
    else
128
629
    {
129
        // #100127# moved real tools::PolyPolygon draw to separate method,
130
        // have to call recursively, avoiding duplicate
131
        // ImplLogicToDevicePixel calls
132
629
        ImplDrawPolyPolygon( nPoly, ImplLogicToDevicePixel( rPolyPoly ) );
133
629
    }
134
926
}
135
136
void OutputDevice::DrawPolygon( const basegfx::B2DPolygon& rB2DPolygon)
137
3.38k
{
138
3.38k
    assert(!is_double_buffered_window());
139
140
    // AW: Do NOT paint empty polygons
141
3.38k
    if(rB2DPolygon.count())
142
3.38k
    {
143
3.38k
        basegfx::B2DPolyPolygon aPP( rB2DPolygon );
144
3.38k
        DrawPolyPolygon( aPP );
145
3.38k
    }
146
3.38k
}
147
148
void OutputDevice::DrawPolygon( const tools::Polygon& rPoly )
149
1.15M
{
150
1.15M
    assert(!is_double_buffered_window());
151
152
1.15M
    if( mpMetaFile )
153
1.13M
        mpMetaFile->AddAction( new MetaPolygonAction( rPoly ) );
154
155
1.15M
    sal_uInt16 nPoints = rPoly.GetSize();
156
157
1.15M
    if ( !IsDeviceOutputNecessary() || (!mbLineColor && !mbFillColor) || (nPoints < 2) || ImplIsRecordLayout() )
158
1.15M
        return;
159
160
    // we need a graphics
161
4.55k
    if ( !mpGraphics && !AcquireGraphics() )
162
0
        return;
163
4.55k
    assert(mpGraphics);
164
165
4.55k
    if ( mbInitClipRegion )
166
535
        InitClipRegion();
167
168
4.55k
    if ( mbOutputClipped )
169
357
        return;
170
171
4.20k
    if ( mbInitLineColor )
172
142
        InitLineColor();
173
174
4.20k
    if ( mbInitFillColor )
175
1.14k
        InitFillColor();
176
177
    // use b2dpolygon drawing if possible
178
4.20k
    if (RasterOp::OverPaint == GetRasterOp() && (IsLineColor() || IsFillColor()))
179
3.52k
    {
180
3.52k
        const basegfx::B2DHomMatrix aTransform(mpMapper->GetDeviceTransformation());
181
3.52k
        basegfx::B2DPolygon aB2DPolygon(rPoly.getB2DPolygon());
182
183
        // ensure closed - maybe assert, hinders buffering
184
3.52k
        if(!aB2DPolygon.isClosed())
185
2.08k
        {
186
2.08k
            aB2DPolygon.setClosed(true);
187
2.08k
        }
188
189
3.52k
        if (IsFillColor())
190
3.21k
        {
191
3.21k
            mpGraphics->DrawPolyPolygon(
192
3.21k
                aTransform,
193
3.21k
                basegfx::B2DPolyPolygon(aB2DPolygon),
194
3.21k
                0.0,
195
3.21k
                *this);
196
3.21k
        }
197
198
3.52k
        bool bSuccess(true);
199
3.52k
        if (IsLineColor())
200
1.87k
        {
201
1.87k
            const bool bPixelSnapHairline(mnAntialiasing & AntialiasingFlags::PixelSnapHairline);
202
203
1.87k
            bSuccess = mpGraphics->DrawPolyLine(
204
1.87k
                aTransform,
205
1.87k
                aB2DPolygon,
206
1.87k
                0.0,
207
1.87k
                0.0, // tdf#124848 hairline
208
1.87k
                nullptr, // MM01
209
1.87k
                basegfx::B2DLineJoin::NONE,
210
1.87k
                css::drawing::LineCap_BUTT,
211
1.87k
                basegfx::deg2rad(15.0), // not used with B2DLineJoin::NONE, but the correct default
212
1.87k
                bPixelSnapHairline,
213
1.87k
                *this);
214
1.87k
        }
215
216
3.52k
        if(bSuccess)
217
3.52k
            return;
218
3.52k
    }
219
220
678
    tools::Polygon aPoly = ImplLogicToDevicePixel( rPoly );
221
678
    const Point* pPtAry = aPoly.GetConstPointAry();
222
223
    // #100127# Forward beziers to sal, if any
224
678
    if( aPoly.HasFlags() )
225
175
    {
226
175
        const PolyFlags* pFlgAry = aPoly.GetConstFlagAry();
227
175
        if( !mpGraphics->DrawPolygonBezier( nPoints, pPtAry, pFlgAry, *this ) )
228
175
        {
229
175
            aPoly = tools::Polygon::SubdivideBezier(aPoly);
230
175
            pPtAry = aPoly.GetConstPointAry();
231
175
            mpGraphics->DrawPolygon( aPoly.GetSize(), pPtAry, *this );
232
175
        }
233
175
    }
234
503
    else
235
503
    {
236
503
        mpGraphics->DrawPolygon( nPoints, pPtAry, *this );
237
503
    }
238
678
}
239
240
// Caution: This method is nearly the same as
241
// OutputDevice::DrawTransparent( const basegfx::B2DPolyPolygon& rB2DPolyPoly, double fTransparency),
242
// so when changes are made here do not forget to make changes there, too
243
244
void OutputDevice::DrawPolyPolygon( const basegfx::B2DPolyPolygon& rB2DPolyPoly )
245
4.38k
{
246
4.38k
    assert(!is_double_buffered_window());
247
248
4.38k
    if( mpMetaFile )
249
4.38k
        mpMetaFile->AddAction( new MetaPolyPolygonAction( tools::PolyPolygon( rB2DPolyPoly ) ) );
250
251
    // call helper
252
4.38k
    ImplDrawPolyPolygonWithB2DPolyPolygon(rB2DPolyPoly);
253
4.38k
}
254
255
void OutputDevice::ImplDrawPolyPolygonWithB2DPolyPolygon(const basegfx::B2DPolyPolygon& rB2DPolyPoly)
256
4.40k
{
257
    // Do not paint empty PolyPolygons
258
4.40k
    if(!rB2DPolyPoly.count() || !IsDeviceOutputNecessary())
259
4.38k
        return;
260
261
    // we need a graphics
262
21
    if( !mpGraphics && !AcquireGraphics() )
263
0
        return;
264
21
    assert(mpGraphics);
265
266
21
    if( mbInitClipRegion )
267
0
        InitClipRegion();
268
269
21
    if( mbOutputClipped )
270
0
        return;
271
272
21
    if( mbInitLineColor )
273
0
        InitLineColor();
274
275
21
    if( mbInitFillColor )
276
0
        InitFillColor();
277
278
21
    bool bSuccess(false);
279
280
21
    if (RasterOp::OverPaint == GetRasterOp() && (IsLineColor() || IsFillColor()))
281
11
    {
282
11
        const basegfx::B2DHomMatrix aTransform(mpMapper->GetDeviceTransformation());
283
11
        basegfx::B2DPolyPolygon aB2DPolyPolygon(rB2DPolyPoly);
284
11
        bSuccess = true;
285
286
        // ensure closed - maybe assert, hinders buffering
287
11
        if(!aB2DPolyPolygon.isClosed())
288
0
        {
289
0
            aB2DPolyPolygon.setClosed(true);
290
0
        }
291
292
11
        if (IsFillColor())
293
11
        {
294
11
            mpGraphics->DrawPolyPolygon(
295
11
                aTransform,
296
11
                aB2DPolyPolygon,
297
11
                0.0,
298
11
                *this);
299
11
        }
300
301
11
        if (IsLineColor())
302
0
        {
303
0
            const bool bPixelSnapHairline(mnAntialiasing & AntialiasingFlags::PixelSnapHairline);
304
305
0
            for(auto const& rPolygon : std::as_const(aB2DPolyPolygon))
306
0
            {
307
0
                bSuccess = mpGraphics->DrawPolyLine(
308
0
                    aTransform,
309
0
                    rPolygon,
310
0
                    (255 - GetLineColor().GetAlpha()) / 255.0,
311
0
                    0.0, // tdf#124848 hairline
312
0
                    nullptr, // MM01
313
0
                    basegfx::B2DLineJoin::NONE,
314
0
                    css::drawing::LineCap_BUTT,
315
0
                    basegfx::deg2rad(15.0), // not used with B2DLineJoin::NONE, but the correct default
316
0
                    bPixelSnapHairline,
317
0
                    *this);
318
0
                if (!bSuccess)
319
0
                    break;
320
0
            }
321
0
        }
322
11
    }
323
324
21
    if (!bSuccess)
325
10
    {
326
        // fallback to old polygon drawing if needed
327
10
        const tools::PolyPolygon aToolsPolyPolygon(rB2DPolyPoly);
328
10
        const tools::PolyPolygon aPixelPolyPolygon = ImplLogicToDevicePixel(aToolsPolyPolygon);
329
10
        ImplDrawPolyPolygon(aPixelPolyPolygon.Count(), aPixelPolyPolygon);
330
10
    }
331
21
}
332
333
// #100127# Extracted from OutputDevice::DrawPolyPolygon()
334
void OutputDevice::ImplDrawPolyPolygon( sal_uInt16 nPoly, const tools::PolyPolygon& rPolyPoly )
335
639
{
336
    // AW: This crashes on empty PolyPolygons, avoid that
337
639
    if(!nPoly)
338
0
        return;
339
340
639
    sal_uInt32 aStackAry1[OUTDEV_POLYPOLY_STACKBUF];
341
639
    const Point* aStackAry2[OUTDEV_POLYPOLY_STACKBUF];
342
639
    const PolyFlags* aStackAry3[OUTDEV_POLYPOLY_STACKBUF];
343
639
    sal_uInt32* pPointAry;
344
639
    const Point**    pPointAryAry;
345
639
    const PolyFlags**  pFlagAryAry;
346
639
    sal_uInt16 i = 0;
347
639
    sal_uInt16 j = 0;
348
639
    sal_uInt16 last = 0;
349
639
    bool bHaveBezier = false;
350
639
    if ( nPoly > OUTDEV_POLYPOLY_STACKBUF )
351
0
    {
352
0
        pPointAry       = new sal_uInt32[nPoly];
353
0
        pPointAryAry    = new const Point*[nPoly];
354
0
        pFlagAryAry     = new const PolyFlags*[nPoly];
355
0
    }
356
639
    else
357
639
    {
358
639
        pPointAry       = aStackAry1;
359
639
        pPointAryAry    = aStackAry2;
360
639
        pFlagAryAry     = aStackAry3;
361
639
    }
362
363
639
    do
364
2.67k
    {
365
2.67k
        const tools::Polygon& rPoly = rPolyPoly.GetObject( i );
366
2.67k
        sal_uInt16 nSize = rPoly.GetSize();
367
2.67k
        if ( nSize )
368
2.67k
        {
369
2.67k
            pPointAry[j] = nSize;
370
2.67k
            pPointAryAry[j] = rPoly.GetConstPointAry();
371
2.67k
            pFlagAryAry[j] = rPoly.GetConstFlagAry();
372
2.67k
            last = i;
373
374
2.67k
            if( pFlagAryAry[j] )
375
1
                bHaveBezier = true;
376
377
2.67k
            ++j;
378
2.67k
        }
379
2.67k
        ++i;
380
2.67k
    }
381
2.67k
    while ( i < nPoly );
382
383
639
    if ( j == 1 )
384
10
    {
385
        // #100127# Forward beziers to sal, if any
386
10
        if( bHaveBezier )
387
1
        {
388
1
            if( !mpGraphics->DrawPolygonBezier( *pPointAry, *pPointAryAry, *pFlagAryAry, *this ) )
389
1
            {
390
1
                tools::Polygon aPoly = tools::Polygon::SubdivideBezier( rPolyPoly.GetObject( last ) );
391
1
                mpGraphics->DrawPolygon( aPoly.GetSize(), aPoly.GetConstPointAry(), *this );
392
1
            }
393
1
        }
394
9
        else
395
9
        {
396
9
            mpGraphics->DrawPolygon( *pPointAry, *pPointAryAry, *this );
397
9
        }
398
10
    }
399
629
    else
400
629
    {
401
        // #100127# Forward beziers to sal, if any
402
629
        if( bHaveBezier )
403
0
        {
404
0
            if (!mpGraphics->DrawPolyPolygonBezier(j, pPointAry, pPointAryAry, pFlagAryAry, *this))
405
0
            {
406
0
                tools::PolyPolygon aPolyPoly = tools::PolyPolygon::SubdivideBezier( rPolyPoly );
407
0
                ImplDrawPolyPolygon( aPolyPoly.Count(), aPolyPoly );
408
0
            }
409
0
        }
410
629
        else
411
629
        {
412
629
            mpGraphics->DrawPolyPolygon( j, pPointAry, pPointAryAry, *this );
413
629
        }
414
629
    }
415
416
639
    if ( pPointAry != aStackAry1 )
417
0
    {
418
0
        delete[] pPointAry;
419
0
        delete[] pPointAryAry;
420
0
        delete[] pFlagAryAry;
421
0
    }
422
639
}
423
424
void OutputDevice::ImplDrawPolygon( const tools::Polygon& rPoly, const tools::PolyPolygon* pClipPolyPoly )
425
6.01M
{
426
6.01M
    if( pClipPolyPoly )
427
0
    {
428
0
        ImplDrawPolyPolygon( tools::PolyPolygon(rPoly), pClipPolyPoly );
429
0
    }
430
6.01M
    else
431
6.01M
    {
432
6.01M
        sal_uInt16 nPoints = rPoly.GetSize();
433
434
6.01M
        if ( nPoints < 2 )
435
0
            return;
436
437
6.01M
        const Point* pPtAry = rPoly.GetConstPointAry();
438
6.01M
        mpGraphics->DrawPolygon( nPoints, pPtAry, *this );
439
6.01M
    }
440
6.01M
}
441
442
void OutputDevice::ImplDrawPolyPolygon( const tools::PolyPolygon& rPolyPoly, const tools::PolyPolygon* pClipPolyPoly )
443
199
{
444
199
    tools::PolyPolygon* pPolyPoly;
445
446
199
    if( pClipPolyPoly )
447
0
    {
448
0
        pPolyPoly = new tools::PolyPolygon;
449
0
        rPolyPoly.GetIntersection( *pClipPolyPoly, *pPolyPoly );
450
0
    }
451
199
    else
452
199
    {
453
199
        pPolyPoly = const_cast<tools::PolyPolygon*>(&rPolyPoly);
454
199
    }
455
199
    if( pPolyPoly->Count() == 1 )
456
0
    {
457
0
        const tools::Polygon& rPoly = pPolyPoly->GetObject( 0 );
458
0
        sal_uInt16 nSize = rPoly.GetSize();
459
460
0
        if( nSize >= 2 )
461
0
        {
462
0
            const Point* pPtAry = rPoly.GetConstPointAry();
463
0
            mpGraphics->DrawPolygon( nSize, pPtAry, *this );
464
0
        }
465
0
    }
466
199
    else if( pPolyPoly->Count() )
467
199
    {
468
199
        sal_uInt16 nCount = pPolyPoly->Count();
469
199
        std::unique_ptr<sal_uInt32[]> pPointAry(new sal_uInt32[nCount]);
470
199
        std::unique_ptr<const Point*[]> pPointAryAry(new const Point*[nCount]);
471
199
        sal_uInt16 i = 0;
472
199
        do
473
398
        {
474
398
            const tools::Polygon& rPoly = pPolyPoly->GetObject( i );
475
398
            sal_uInt16 nSize = rPoly.GetSize();
476
398
            if ( nSize )
477
398
            {
478
398
                pPointAry[i] = nSize;
479
398
                pPointAryAry[i] = rPoly.GetConstPointAry();
480
398
                i++;
481
398
            }
482
0
            else
483
0
                nCount--;
484
398
        }
485
398
        while( i < nCount );
486
487
199
        if( nCount == 1 )
488
0
            mpGraphics->DrawPolygon( pPointAry[0], pPointAryAry[0], *this );
489
199
        else
490
199
            mpGraphics->DrawPolyPolygon( nCount, pPointAry.get(), pPointAryAry.get(), *this );
491
199
    }
492
493
199
    if( pClipPolyPoly )
494
0
        delete pPolyPoly;
495
199
}
496
497
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */