Coverage Report

Created: 2026-02-14 09:37

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