Coverage Report

Created: 2026-08-14 10:22

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/libreoffice/vcl/source/outdev/polyline.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 <basegfx/polygon/b2dlinegeometry.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
33
void OutputDevice::DrawPolyLine( const tools::Polygon& rPoly )
34
178k
{
35
178k
    assert(!is_double_buffered_window());
36
37
178k
    if( mpMetaFile )
38
14.8k
        mpMetaFile->AddAction( new MetaPolyLineAction( rPoly ) );
39
40
178k
    sal_uInt16 nPoints = rPoly.GetSize();
41
42
178k
    if ( !IsDeviceOutputNecessary() || !mbLineColor || (nPoints < 2) || ImplIsRecordLayout() )
43
27.1k
        return;
44
45
    // we need a graphics
46
150k
    if ( !mpGraphics && !AcquireGraphics() )
47
0
        return;
48
150k
    assert(mpGraphics);
49
50
150k
    if ( mbInitClipRegion )
51
27
        InitClipRegion();
52
53
150k
    if ( mbOutputClipped )
54
24
        return;
55
56
150k
    if ( mbInitLineColor )
57
35
        InitLineColor();
58
59
    // use b2dpolygon drawing if possible
60
150k
    if(DrawPolyLineDirectInternal(
61
150k
        basegfx::B2DHomMatrix(),
62
150k
        rPoly.getB2DPolygon()))
63
105k
    {
64
105k
        return;
65
105k
    }
66
67
44.9k
    const basegfx::B2DPolygon aB2DPolyLine(rPoly.getB2DPolygon());
68
44.9k
    const basegfx::B2DHomMatrix aTransform(mpMapper->GetDeviceTransformation());
69
44.9k
    const bool bPixelSnapHairline(mnAntialiasing & AntialiasingFlags::PixelSnapHairline);
70
71
44.9k
    bool bDrawn = mpGraphics->DrawPolyLine(
72
44.9k
        aTransform,
73
44.9k
        aB2DPolyLine,
74
44.9k
        0.0,
75
44.9k
        0.0, // tdf#124848 hairline
76
44.9k
        nullptr, // MM01
77
44.9k
        basegfx::B2DLineJoin::NONE,
78
44.9k
        css::drawing::LineCap_BUTT,
79
44.9k
        basegfx::deg2rad(15.0) /*default fMiterMinimumAngle, not used*/,
80
44.9k
        bPixelSnapHairline,
81
44.9k
        *this);
82
83
44.9k
    if(!bDrawn)
84
0
    {
85
0
        tools::Polygon aPoly = ImplLogicToDevicePixel( rPoly );
86
0
        Point* pPtAry = aPoly.GetPointAry();
87
88
        // #100127# Forward beziers to sal, if any
89
0
        if( aPoly.HasFlags() )
90
0
        {
91
0
            const PolyFlags* pFlgAry = aPoly.GetConstFlagAry();
92
0
            if( !mpGraphics->DrawPolyLineBezier( nPoints, pPtAry, pFlgAry, *this ) )
93
0
            {
94
0
                aPoly = tools::Polygon::SubdivideBezier(aPoly);
95
0
                pPtAry = aPoly.GetPointAry();
96
0
                mpGraphics->DrawPolyLine( aPoly.GetSize(), pPtAry, *this );
97
0
            }
98
0
        }
99
0
        else
100
0
        {
101
0
            mpGraphics->DrawPolyLine( nPoints, pPtAry, *this );
102
0
        }
103
0
    }
104
44.9k
}
105
106
void OutputDevice::DrawPolyLine( const tools::Polygon& rPoly, const LineInfo& rLineInfo )
107
958k
{
108
958k
    assert(!is_double_buffered_window());
109
110
958k
    if ( rLineInfo.IsDefault() )
111
0
    {
112
0
        DrawPolyLine( rPoly );
113
0
        return;
114
0
    }
115
116
958k
    if (IsDeviceOutputNecessary())
117
1.16k
    {
118
1.16k
        auto eLineStyle = rLineInfo.GetStyle();
119
1.16k
        switch (eLineStyle)
120
1.16k
        {
121
326
            case LineStyle::NONE:
122
610
            case LineStyle::Dash:
123
                // use drawPolyLine for these
124
610
                break;
125
523
            case LineStyle::Solid:
126
                // #i101491# Try direct Fallback to B2D-Version of DrawPolyLine
127
523
                DrawPolyLine(
128
523
                    rPoly.getB2DPolygon(),
129
523
                    rLineInfo.GetWidth(),
130
523
                    rLineInfo.GetLineJoin(),
131
523
                    rLineInfo.GetLineCap(),
132
523
                    basegfx::deg2rad(15.0) /* default fMiterMinimumAngle, value not available in LineInfo */);
133
523
                return;
134
28
            default:
135
28
                SAL_WARN("vcl.gdi", "Unknown LineStyle: " << static_cast<int>(eLineStyle));
136
28
                return;
137
1.16k
        }
138
1.16k
    }
139
140
957k
    if ( mpMetaFile )
141
957k
        mpMetaFile->AddAction( new MetaPolyLineAction( rPoly, rLineInfo ) );
142
143
957k
    drawPolyLine(rPoly, rLineInfo);
144
957k
}
145
146
void OutputDevice::DrawPolyLine( const basegfx::B2DPolygon& rB2DPolygon,
147
                                 double fLineWidth,
148
                                 basegfx::B2DLineJoin eLineJoin,
149
                                 css::drawing::LineCap eLineCap,
150
                                 double fMiterMinimumAngle)
151
1.21k
{
152
1.21k
    assert(!is_double_buffered_window());
153
154
1.21k
    if( mpMetaFile )
155
691
    {
156
691
        LineInfo aLineInfo;
157
691
        if( fLineWidth != 0.0 )
158
424
            aLineInfo.SetWidth( fLineWidth );
159
160
691
        aLineInfo.SetLineJoin(eLineJoin);
161
691
        aLineInfo.SetLineCap(eLineCap);
162
163
691
        tools::Polygon aToolsPolygon( rB2DPolygon );
164
691
        mpMetaFile->AddAction( new MetaPolyLineAction( std::move(aToolsPolygon), std::move(aLineInfo) ) );
165
691
    }
166
167
    // Do not paint empty PolyPolygons
168
1.21k
    if(!rB2DPolygon.count() || !IsDeviceOutputNecessary())
169
693
        return;
170
171
    // we need a graphics
172
521
    if( !mpGraphics && !AcquireGraphics() )
173
0
        return;
174
521
    assert(mpGraphics);
175
176
521
    if( mbInitClipRegion )
177
13
        InitClipRegion();
178
179
521
    if( mbOutputClipped )
180
0
        return;
181
182
521
    if( mbInitLineColor )
183
74
        InitLineColor();
184
185
    // use b2dpolygon drawing if possible
186
521
    if(DrawPolyLineDirectInternal(
187
521
        basegfx::B2DHomMatrix(),
188
521
        rB2DPolygon,
189
521
        fLineWidth,
190
521
        0.0,
191
521
        nullptr, // MM01
192
521
        eLineJoin,
193
521
        eLineCap,
194
521
        fMiterMinimumAngle))
195
499
    {
196
499
        return;
197
499
    }
198
199
    // #i101491#
200
    // no output yet; fallback to geometry decomposition and use filled polygon paint
201
    // when line is fat and not too complex. ImplDrawPolyPolygonWithB2DPolyPolygon
202
    // will do internal needed AA checks etc.
203
22
    if(fLineWidth >= 2.5 &&
204
17
       rB2DPolygon.count() &&
205
17
       rB2DPolygon.count() <= 1000)
206
15
    {
207
15
        const double fHalfLineWidth((fLineWidth * 0.5) + 0.5);
208
15
        const basegfx::B2DPolyPolygon aAreaPolyPolygon(
209
15
                basegfx::utils::createAreaGeometry( rB2DPolygon,
210
15
                                                    fHalfLineWidth,
211
15
                                                    eLineJoin,
212
15
                                                    eLineCap,
213
15
                                                    fMiterMinimumAngle));
214
15
        const Color aOldLineColor(maLineColor);
215
15
        const Color aOldFillColor(maFillColor);
216
217
15
        SetLineColor();
218
15
        InitLineColor();
219
15
        SetFillColor(aOldLineColor);
220
15
        InitFillColor();
221
222
        // draw using a loop; else the topology will paint a PolyPolygon
223
15
        for(auto const& rPolygon : aAreaPolyPolygon)
224
21
        {
225
21
            ImplDrawPolyPolygonWithB2DPolyPolygon(
226
21
                basegfx::B2DPolyPolygon(rPolygon));
227
21
        }
228
229
15
        SetLineColor(aOldLineColor);
230
15
        InitLineColor();
231
15
        SetFillColor(aOldFillColor);
232
15
        InitFillColor();
233
234
        // when AA it is necessary to also paint the filled polygon's outline
235
        // to avoid optical gaps
236
15
        for(auto const& rPolygon : aAreaPolyPolygon)
237
21
        {
238
21
            (void)DrawPolyLineDirectInternal(
239
21
                basegfx::B2DHomMatrix(),
240
21
                rPolygon);
241
21
        }
242
15
    }
243
7
    else
244
7
    {
245
        // fallback to old polygon drawing if needed
246
7
        const tools::Polygon aToolsPolygon( rB2DPolygon );
247
7
        LineInfo aLineInfo;
248
7
        if( fLineWidth != 0.0 )
249
7
            aLineInfo.SetWidth( fLineWidth );
250
251
7
        drawPolyLine( aToolsPolygon, aLineInfo );
252
7
    }
253
22
}
254
255
void OutputDevice::drawPolyLine(const tools::Polygon& rPoly, const LineInfo& rLineInfo)
256
957k
{
257
957k
    sal_uInt16 nPoints(rPoly.GetSize());
258
259
957k
    if ( !IsDeviceOutputNecessary() || !mbLineColor || ( nPoints < 2 ) || ( LineStyle::NONE == rLineInfo.GetStyle() ) || ImplIsRecordLayout() )
260
957k
        return;
261
262
    // we need a graphics
263
291
    if ( !mpGraphics && !AcquireGraphics() )
264
0
        return;
265
291
    assert(mpGraphics);
266
267
291
    if ( mbInitClipRegion )
268
19
        InitClipRegion();
269
270
291
    if ( mbOutputClipped )
271
70
        return;
272
273
221
    if ( mbInitLineColor )
274
24
        InitLineColor();
275
276
221
    const LineInfo aInfo( ImplLogicToDevicePixel( rLineInfo ) );
277
221
    const bool bDashUsed(LineStyle::Dash == aInfo.GetStyle());
278
221
    const bool bLineWidthUsed(aInfo.GetWidth() > 1);
279
280
221
    if (bDashUsed || bLineWidthUsed)
281
63
    {
282
63
        basegfx::B2DPolygon aPoly = ImplLogicToDevicePixel(rPoly.getB2DPolygon());
283
63
        drawLine(basegfx::B2DPolyPolygon(aPoly), aInfo);
284
63
    }
285
158
    else
286
158
    {
287
158
        tools::Polygon aPoly = ImplLogicToDevicePixel(rPoly);
288
289
        // #100127# the subdivision HAS to be done here since only a pointer
290
        // to an array of points is given to the DrawPolyLine method, there is
291
        // NO way to find out there that it's a curve.
292
158
        if( aPoly.HasFlags() )
293
1
        {
294
1
            aPoly = tools::Polygon::SubdivideBezier( aPoly );
295
1
            nPoints = aPoly.GetSize();
296
1
        }
297
298
158
        mpGraphics->DrawPolyLine(nPoints, aPoly.GetPointAry(), *this);
299
158
    }
300
221
}
301
302
bool OutputDevice::DrawPolyLineDirect(
303
    const basegfx::B2DHomMatrix& rObjectTransform,
304
    const basegfx::B2DPolygon& rB2DPolygon,
305
    double fLineWidth,
306
    double fTransparency,
307
    const std::vector< double >* pStroke, // MM01
308
    basegfx::B2DLineJoin eLineJoin,
309
    css::drawing::LineCap eLineCap,
310
    double fMiterMinimumAngle)
311
0
{
312
0
    if(DrawPolyLineDirectInternal(rObjectTransform, rB2DPolygon, fLineWidth, fTransparency,
313
0
        pStroke, eLineJoin, eLineCap, fMiterMinimumAngle))
314
0
    {
315
        // Worked, add metafile action (if recorded). This is done only here,
316
        // because this function is public, other OutDev functions already add metafile
317
        // actions, so they call the internal function directly.
318
0
        if( mpMetaFile )
319
0
        {
320
0
            LineInfo aLineInfo;
321
0
            if( fLineWidth != 0.0 )
322
0
                aLineInfo.SetWidth( fLineWidth );
323
            // Transport known information, might be needed
324
0
            aLineInfo.SetLineJoin(eLineJoin);
325
0
            aLineInfo.SetLineCap(eLineCap);
326
            // MiterMinimumAngle does not exist yet in LineInfo
327
0
            tools::Polygon aToolsPolygon( rB2DPolygon );
328
0
            mpMetaFile->AddAction( new MetaPolyLineAction( std::move(aToolsPolygon), std::move(aLineInfo) ) );
329
0
        }
330
0
        return true;
331
0
    }
332
0
    return false;
333
0
}
334
335
bool OutputDevice::DrawPolyLineDirectInternal(
336
    const basegfx::B2DHomMatrix& rObjectTransform,
337
    const basegfx::B2DPolygon& rB2DPolygon,
338
    double fLineWidth,
339
    double fTransparency,
340
    const std::vector< double >* pStroke, // MM01
341
    basegfx::B2DLineJoin eLineJoin,
342
    css::drawing::LineCap eLineCap,
343
    double fMiterMinimumAngle)
344
151k
{
345
151k
    assert(!is_double_buffered_window());
346
347
    // AW: Do NOT paint empty PolyPolygons
348
151k
    if(!rB2DPolygon.count())
349
0
        return true;
350
351
    // we need a graphics
352
151k
    if( !mpGraphics && !AcquireGraphics() )
353
0
        return false;
354
151k
    assert(mpGraphics);
355
356
151k
    if( mbInitClipRegion )
357
0
        InitClipRegion();
358
359
151k
    if( mbOutputClipped )
360
0
        return true;
361
362
151k
    if( mbInitLineColor )
363
0
        InitLineColor();
364
365
151k
    const bool bTryB2d(RasterOp::OverPaint == GetRasterOp() && IsLineColor());
366
367
151k
    if(bTryB2d)
368
106k
    {
369
        // combine rObjectTransform with WorldToDevice
370
106k
        const basegfx::B2DHomMatrix aTransform(mpMapper->GetDeviceTransformation() * rObjectTransform);
371
106k
        const bool bPixelSnapHairline((mnAntialiasing & AntialiasingFlags::PixelSnapHairline) && rB2DPolygon.count() < 1000);
372
373
        // draw the polyline
374
106k
        return mpGraphics->DrawPolyLine(
375
106k
            aTransform,
376
106k
            rB2DPolygon,
377
106k
            fTransparency,
378
106k
            fLineWidth, // tdf#124848 use LineWidth direct, do not try to solve for zero-case (aka hairline)
379
106k
            pStroke, // MM01
380
106k
            eLineJoin,
381
106k
            eLineCap,
382
106k
            fMiterMinimumAngle,
383
106k
            bPixelSnapHairline,
384
106k
            *this);
385
106k
    }
386
44.9k
    return false;
387
151k
}
388
389
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */