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/hatch.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 <osl/diagnose.h>
21
#include <tools/line.hxx>
22
#include <tools/helpers.hxx>
23
#include <comphelper/configuration.hxx>
24
25
#include <vcl/hatch.hxx>
26
#include <vcl/metaact.hxx>
27
#include <vcl/settings.hxx>
28
#include <vcl/virdev.hxx>
29
30
#include <drawmode.hxx>
31
#include <salgdi.hxx>
32
33
#include <cassert>
34
#include <cstdlib>
35
#include <memory>
36
37
3.56M
#define HATCH_MAXPOINTS             1024
38
39
extern "C" {
40
41
static int HatchCmpFnc( const void* p1, const void* p2 )
42
21.7M
{
43
21.7M
    const tools::Long nX1 = static_cast<Point const *>(p1)->X();
44
21.7M
    const tools::Long nX2 = static_cast<Point const *>(p2)->X();
45
21.7M
    const tools::Long nY1 = static_cast<Point const *>(p1)->Y();
46
21.7M
    const tools::Long nY2 = static_cast<Point const *>(p2)->Y();
47
48
21.7M
    return ( nX1 > nX2 ? 1 : nX1 == nX2 ? nY1 > nY2 ? 1: nY1 == nY2 ? 0 : -1 : -1 );
49
21.7M
}
50
51
}
52
53
void OutputDevice::DrawHatch( const tools::PolyPolygon& rPolyPoly, const Hatch& rHatch )
54
9.45k
{
55
9.45k
    assert(!is_double_buffered_window());
56
57
9.45k
    Hatch aHatch( rHatch );
58
9.45k
    aHatch.SetColor(vcl::drawmode::GetHatchColor(rHatch.GetColor(), GetDrawMode(), GetSettings().GetStyleSettings()));
59
60
9.45k
    if( mpMetaFile )
61
0
        mpMetaFile->AddAction( new MetaHatchAction( rPolyPoly, aHatch ) );
62
63
9.45k
    if( !IsDeviceOutputNecessary() || ImplIsRecordLayout() )
64
0
        return;
65
66
9.45k
    if( !mpGraphics && !AcquireGraphics() )
67
0
        return;
68
9.45k
    assert(mpGraphics);
69
70
9.45k
    if( mbInitClipRegion )
71
1.87k
        InitClipRegion();
72
73
9.45k
    if( mbOutputClipped )
74
1.24k
        return;
75
76
8.20k
    if( rPolyPoly.Count() )
77
1.37k
    {
78
1.37k
        tools::PolyPolygon     aPolyPoly( LogicToPixel( rPolyPoly ) );
79
1.37k
        GDIMetaFile*    pOldMetaFile = mpMetaFile;
80
1.37k
        bool            bOldMap = mbMap;
81
82
1.37k
        aPolyPoly.Optimize( PolyOptimizeFlags::NO_SAME );
83
1.37k
        aHatch.SetDistance( ImplLogicWidthToDevicePixel( aHatch.GetDistance() ) );
84
85
1.37k
        mpMetaFile = nullptr;
86
1.37k
        EnableMapMode( false );
87
1.37k
        Push( vcl::PushFlags::LINECOLOR );
88
1.37k
        SetLineColor( aHatch.GetColor() );
89
1.37k
        InitLineColor();
90
1.37k
        DrawHatch( aPolyPoly, aHatch, false );
91
1.37k
        Pop();
92
1.37k
        EnableMapMode( bOldMap );
93
1.37k
        mpMetaFile = pOldMetaFile;
94
1.37k
    }
95
8.20k
}
96
97
void OutputDevice::AddHatchActions( const tools::PolyPolygon& rPolyPoly, const Hatch& rHatch,
98
                                    GDIMetaFile& rMtf )
99
0
{
100
101
0
    tools::PolyPolygon aPolyPoly( rPolyPoly );
102
0
    aPolyPoly.Optimize( PolyOptimizeFlags::NO_SAME | PolyOptimizeFlags::CLOSE );
103
104
0
    if( aPolyPoly.Count() )
105
0
    {
106
0
        GDIMetaFile* pOldMtf = mpMetaFile;
107
108
0
        mpMetaFile = &rMtf;
109
0
        mpMetaFile->AddAction( new MetaPushAction( vcl::PushFlags::ALL ) );
110
0
        mpMetaFile->AddAction( new MetaLineColorAction( rHatch.GetColor(), true ) );
111
0
        DrawHatch( aPolyPoly, rHatch, true );
112
0
        mpMetaFile->AddAction( new MetaPopAction() );
113
0
        mpMetaFile = pOldMtf;
114
0
    }
115
0
}
116
117
static bool HasSaneNSteps(const Point& rPt1, const Point& rEndPt1, const Size& rInc)
118
1.70k
{
119
1.70k
    tools::Long nVertSteps = -1;
120
1.70k
    if (rInc.Height())
121
1.36k
    {
122
1.36k
        bool bFail = o3tl::checked_sub(rEndPt1.Y(), rPt1.Y(), nVertSteps);
123
1.36k
        if (bFail)
124
0
            nVertSteps = std::numeric_limits<tools::Long>::max();
125
1.36k
        else
126
1.36k
            nVertSteps = nVertSteps / rInc.Height();
127
1.36k
    }
128
1.70k
    tools::Long nHorzSteps = -1;
129
1.70k
    if (rInc.Width())
130
347
    {
131
347
        bool bFail = o3tl::checked_sub(rEndPt1.X(), rPt1.X(), nHorzSteps);
132
347
        if (bFail)
133
0
            nHorzSteps = std::numeric_limits<tools::Long>::max();
134
347
        else
135
347
            nHorzSteps = nHorzSteps / rInc.Width();
136
347
    }
137
1.70k
    auto nSteps = std::max(nVertSteps, nHorzSteps);
138
1.70k
    if (nSteps > 1024)
139
211
    {
140
211
        SAL_WARN("vcl.gdi", "skipping slow hatch with " << nSteps << " steps");
141
211
        return false;
142
211
    }
143
1.49k
    return true;
144
1.70k
}
145
146
void OutputDevice::DrawHatch( const tools::PolyPolygon& rPolyPoly, const Hatch& rHatch, bool bMtf )
147
1.37k
{
148
1.37k
    assert(!is_double_buffered_window());
149
150
1.37k
    if(!rPolyPoly.Count())
151
0
        return;
152
153
    // #i115630# DrawHatch does not work with beziers included in the polypolygon, take care of that
154
1.37k
    bool bIsCurve(false);
155
156
2.99k
    for(sal_uInt16 a(0); !bIsCurve && a < rPolyPoly.Count(); a++)
157
1.61k
    {
158
1.61k
        if(rPolyPoly[a].HasFlags())
159
0
        {
160
0
            bIsCurve = true;
161
0
        }
162
1.61k
    }
163
164
1.37k
    if(bIsCurve)
165
0
    {
166
0
        OSL_ENSURE(false, "DrawHatch does *not* support curves, falling back to AdaptiveSubdivide()...");
167
0
        tools::PolyPolygon aPolyPoly;
168
169
0
        rPolyPoly.AdaptiveSubdivide(aPolyPoly);
170
0
        DrawHatch(aPolyPoly, rHatch, bMtf);
171
172
0
        return;
173
0
    }
174
175
1.37k
    tools::Rectangle   aRect( rPolyPoly.GetBoundRect() );
176
1.37k
    const tools::Long  nLogPixelWidth = ImplDevicePixelToLogicWidth( 1 );
177
1.37k
    const tools::Long  nWidth = ImplDevicePixelToLogicWidth( std::max( ImplLogicWidthToDevicePixel( rHatch.GetDistance() ), tools::Long(3) ) );
178
1.37k
    std::unique_ptr<Point[]> pPtBuffer(new Point[ HATCH_MAXPOINTS ]);
179
1.37k
    Point       aPt1, aPt2, aEndPt1;
180
1.37k
    Size        aInc;
181
182
    // Single hatch
183
1.37k
    aRect.AdjustLeft( -nLogPixelWidth ); aRect.AdjustTop( -nLogPixelWidth ); aRect.AdjustRight(nLogPixelWidth ); aRect.AdjustBottom(nLogPixelWidth );
184
1.37k
    CalcHatchValues( aRect, nWidth, rHatch.GetAngle(), aPt1, aPt2, aInc, aEndPt1 );
185
1.37k
    if (comphelper::IsFuzzing() && !HasSaneNSteps(aPt1, aEndPt1, aInc))
186
82
        return;
187
188
1.29k
    if (aInc.Width() <= 0 && aInc.Height() <= 0)
189
1.29k
        SAL_WARN("vcl.gdi", "invalid increment");
190
1.29k
    else
191
1.29k
    {
192
1.29k
        do
193
79.6k
        {
194
79.6k
            DrawHatchLine( tools::Line( aPt1, aPt2 ), rPolyPoly, pPtBuffer.get(), bMtf );
195
79.6k
            aPt1.AdjustX(aInc.Width() ); aPt1.AdjustY(aInc.Height() );
196
79.6k
            aPt2.AdjustX(aInc.Width() ); aPt2.AdjustY(aInc.Height() );
197
79.6k
        }
198
79.6k
        while( ( aPt1.X() <= aEndPt1.X() ) && ( aPt1.Y() <= aEndPt1.Y() ) );
199
1.29k
    }
200
201
1.29k
    if (rHatch.GetStyle() != HatchStyle::Double && rHatch.GetStyle() != HatchStyle::Triple)
202
1.02k
        return;
203
204
    // Double hatch
205
273
    CalcHatchValues( aRect, nWidth, rHatch.GetAngle() + 900_deg10, aPt1, aPt2, aInc, aEndPt1 );
206
273
    if (comphelper::IsFuzzing() && !HasSaneNSteps(aPt1, aEndPt1, aInc))
207
120
        return;
208
209
153
    do
210
9.74k
    {
211
9.74k
        DrawHatchLine( tools::Line( aPt1, aPt2 ), rPolyPoly, pPtBuffer.get(), bMtf );
212
9.74k
        aPt1.AdjustX(aInc.Width() ); aPt1.AdjustY(aInc.Height() );
213
9.74k
        aPt2.AdjustX(aInc.Width() ); aPt2.AdjustY(aInc.Height() );
214
9.74k
    }
215
9.74k
    while( ( aPt1.X() <= aEndPt1.X() ) && ( aPt1.Y() <= aEndPt1.Y() ) );
216
217
153
    if( rHatch.GetStyle() == HatchStyle::Triple )
218
59
    {
219
        // Triple hatch
220
59
        CalcHatchValues( aRect, nWidth, rHatch.GetAngle() + 450_deg10, aPt1, aPt2, aInc, aEndPt1 );
221
59
        if (comphelper::IsFuzzing() && !HasSaneNSteps(aPt1, aEndPt1, aInc))
222
9
            return;
223
224
50
        do
225
1.01k
        {
226
1.01k
            DrawHatchLine( tools::Line( aPt1, aPt2 ), rPolyPoly, pPtBuffer.get(), bMtf );
227
1.01k
            aPt1.AdjustX(aInc.Width() ); aPt1.AdjustY(aInc.Height() );
228
1.01k
            aPt2.AdjustX(aInc.Width() ); aPt2.AdjustY(aInc.Height() );
229
1.01k
        }
230
1.01k
        while( ( aPt1.X() <= aEndPt1.X() ) && ( aPt1.Y() <= aEndPt1.Y() ) );
231
50
    }
232
153
}
233
234
void OutputDevice::CalcHatchValues( const tools::Rectangle& rRect, tools::Long nDist, Degree10 nAngle10,
235
                                    Point& rPt1, Point& rPt2, Size& rInc, Point& rEndPt1 )
236
1.70k
{
237
1.70k
    Point   aRef;
238
1.70k
    Degree10    nAngle = nAngle10 % 1800_deg10;
239
1.70k
    tools::Long    nOffset = 0;
240
241
1.70k
    if( nAngle > 900_deg10 )
242
124
        nAngle -= 1800_deg10;
243
244
1.70k
    aRef = ( !IsRefPoint() ? rRect.TopLeft() : GetRefPoint() );
245
246
1.70k
    if( 0_deg10 == nAngle )
247
1.08k
    {
248
1.08k
        rInc = Size( 0, nDist );
249
1.08k
        rPt1 = rRect.TopLeft();
250
1.08k
        rPt2 = rRect.TopRight();
251
1.08k
        rEndPt1 = rRect.BottomLeft();
252
253
1.08k
        if( aRef.Y() <= rRect.Top() )
254
358
            nOffset = ( ( rRect.Top() - aRef.Y() ) % nDist );
255
730
        else
256
730
            nOffset = ( nDist - ( ( aRef.Y() - rRect.Top() ) % nDist ) );
257
258
1.08k
        rPt1.AdjustY( -nOffset );
259
1.08k
        rPt2.AdjustY( -nOffset );
260
1.08k
    }
261
620
    else if( 900_deg10 == nAngle )
262
206
    {
263
206
        rInc = Size( nDist, 0 );
264
206
        rPt1 = rRect.TopLeft();
265
206
        rPt2 = rRect.BottomLeft();
266
206
        rEndPt1 = rRect.TopRight();
267
268
206
        if( aRef.X() <= rRect.Left() )
269
18
            nOffset = ( rRect.Left() - aRef.X() ) % nDist;
270
188
        else
271
188
            nOffset = nDist - ( ( aRef.X() - rRect.Left() ) % nDist );
272
273
206
        rPt1.AdjustX( -nOffset );
274
206
        rPt2.AdjustX( -nOffset );
275
206
    }
276
414
    else if( nAngle >= Degree10(-450) && nAngle <= 450_deg10 )
277
273
    {
278
273
        const double    fAngle = std::abs( toRadians(nAngle) );
279
273
        const double    fTan = tan( fAngle );
280
273
        const tools::Long      nYOff = basegfx::fround<tools::Long>( ( rRect.Right() - rRect.Left() ) * fTan );
281
273
        tools::Long            nPY;
282
283
273
        nDist = basegfx::fround<tools::Long>(nDist / cos(fAngle));
284
273
        rInc = Size( 0, nDist );
285
286
273
        if( nAngle > 0_deg10 )
287
100
        {
288
100
            rPt1 = rRect.TopLeft();
289
100
            rPt2 = Point( rRect.Right(), rRect.Top() - nYOff );
290
100
            rEndPt1 = Point( rRect.Left(), rRect.Bottom() + nYOff );
291
100
            nPY = basegfx::fround<tools::Long>(aRef.Y() - ((rPt1.X() - aRef.X()) * fTan));
292
100
        }
293
173
        else
294
173
        {
295
173
            rPt1 = rRect.TopRight();
296
173
            rPt2 = Point( rRect.Left(), rRect.Top() - nYOff );
297
173
            rEndPt1 = Point( rRect.Right(), rRect.Bottom() + nYOff );
298
173
            nPY = basegfx::fround<tools::Long>(aRef.Y() + ((rPt1.X() - aRef.X()) * fTan));
299
173
        }
300
301
273
        if( nPY <= rPt1.Y() )
302
100
            nOffset = ( rPt1.Y() - nPY ) % nDist;
303
173
        else
304
173
            nOffset = nDist - ( ( nPY - rPt1.Y() ) % nDist );
305
306
273
        rPt1.AdjustY( -nOffset );
307
273
        rPt2.AdjustY( -nOffset );
308
273
    }
309
141
    else
310
141
    {
311
141
        const double fAngle = std::abs( toRadians(nAngle) );
312
141
        const double fTan = tan( fAngle );
313
141
        const tools::Long   nXOff = basegfx::fround<tools::Long>( (static_cast<double>(rRect.Bottom()) - rRect.Top()) / fTan );
314
141
        tools::Long         nPX;
315
316
141
        nDist = basegfx::fround<tools::Long>(nDist / sin(fAngle));
317
141
        rInc = Size( nDist, 0 );
318
319
141
        if( nAngle > 0_deg10 )
320
73
        {
321
73
            rPt1 = rRect.TopLeft();
322
73
            rPt2 = Point( rRect.Left() - nXOff, rRect.Bottom() );
323
73
            rEndPt1 = Point( rRect.Right() + nXOff, rRect.Top() );
324
73
            nPX = basegfx::fround<tools::Long>( aRef.X() - ( (static_cast<double>(rPt1.Y()) - aRef.Y()) / fTan ) );
325
73
        }
326
68
        else
327
68
        {
328
68
            rPt1 = rRect.BottomLeft();
329
68
            rPt2 = Point( rRect.Left() - nXOff, rRect.Top() );
330
68
            rEndPt1 = Point( rRect.Right() + nXOff, rRect.Bottom() );
331
68
            nPX = basegfx::fround<tools::Long>( aRef.X() + ( (static_cast<double>(rPt1.Y()) - aRef.Y()) / fTan ) );
332
68
        }
333
334
141
        if( nPX <= rPt1.X() )
335
117
            nOffset = ( rPt1.X() - nPX ) % nDist;
336
24
        else
337
24
            nOffset = nDist - ( ( nPX - rPt1.X() ) % nDist );
338
339
141
        rPt1.AdjustX( -nOffset );
340
141
        rPt2.AdjustX( -nOffset );
341
141
    }
342
1.70k
}
343
344
void OutputDevice::DrawHatchLine( const tools::Line& rLine, const tools::PolyPolygon& rPolyPoly,
345
                                      Point* pPtBuffer, bool bMtf )
346
90.4k
{
347
90.4k
    assert(!is_double_buffered_window());
348
349
90.4k
    double  fX, fY;
350
90.4k
    tools::Long    nAdd, nPCounter = 0;
351
352
198k
    for( tools::Long nPoly = 0, nPolyCount = rPolyPoly.Count(); nPoly < nPolyCount; nPoly++ )
353
108k
    {
354
108k
        const tools::Polygon& rPoly = rPolyPoly[ static_cast<sal_uInt16>(nPoly) ];
355
356
108k
        if( rPoly.GetSize() > 1 )
357
106k
        {
358
106k
            tools::Line aCurSegment( rPoly[ 0 ], Point() );
359
360
17.8M
            for( tools::Long i = 1, nCount = rPoly.GetSize(); i <= nCount; i++ )
361
17.7M
            {
362
17.7M
                aCurSegment.SetEnd( rPoly[ static_cast<sal_uInt16>( i % nCount ) ] );
363
17.7M
                nAdd = 0;
364
365
17.7M
                if( rLine.Intersection( aCurSegment, fX, fY ) )
366
3.57M
                {
367
3.57M
                    if( ( fabs( fX - aCurSegment.GetStart().X() ) <= 0.0000001 ) &&
368
433k
                        ( fabs( fY - aCurSegment.GetStart().Y() ) <= 0.0000001 ) )
369
5.72k
                    {
370
5.72k
                        const tools::Line aPrevSegment( rPoly[ static_cast<sal_uInt16>( ( i > 1 ) ? ( i - 2 ) : ( nCount - 1 ) ) ], aCurSegment.GetStart() );
371
5.72k
                        const double    fPrevDistance = rLine.GetDistance( aPrevSegment.GetStart() );
372
5.72k
                        const double    fCurDistance = rLine.GetDistance( aCurSegment.GetEnd() );
373
374
5.72k
                        if( ( fPrevDistance <= 0.0 && fCurDistance > 0.0 ) ||
375
4.91k
                            ( fPrevDistance > 0.0 && fCurDistance < 0.0 ) )
376
1.55k
                        {
377
1.55k
                            nAdd = 1;
378
1.55k
                        }
379
5.72k
                    }
380
3.56M
                    else if( ( fabs( fX - aCurSegment.GetEnd().X() ) <= 0.0000001 ) &&
381
432k
                             ( fabs( fY - aCurSegment.GetEnd().Y() ) <= 0.0000001 ) )
382
5.72k
                    {
383
5.72k
                        const tools::Line aNextSegment( aCurSegment.GetEnd(), rPoly[ static_cast<sal_uInt16>( ( i + 1 ) % nCount ) ] );
384
385
5.72k
                        if( ( fabs( rLine.GetDistance( aNextSegment.GetEnd() ) ) <= 0.0000001 ) &&
386
537
                            ( rLine.GetDistance( aCurSegment.GetStart() ) > 0.0 ) )
387
136
                        {
388
136
                            nAdd = 1;
389
136
                        }
390
5.72k
                    }
391
3.56M
                    else
392
3.56M
                        nAdd = 1;
393
394
3.57M
                    if( nAdd )
395
3.56M
                    {
396
3.56M
                        if (nPCounter == HATCH_MAXPOINTS)
397
359
                        {
398
359
                            SAL_WARN("vcl.gdi", "too many hatch points");
399
359
                            return;
400
359
                        }
401
3.56M
                        pPtBuffer[nPCounter++] = Point(basegfx::fround<tools::Long>(fX),
402
3.56M
                                                       basegfx::fround<tools::Long>(fY));
403
3.56M
                    }
404
3.57M
                }
405
406
17.7M
                aCurSegment.SetStart( aCurSegment.GetEnd() );
407
17.7M
            }
408
106k
        }
409
108k
    }
410
411
90.0k
    if( nPCounter <= 1 )
412
2.91k
        return;
413
414
87.1k
    qsort( pPtBuffer, nPCounter, sizeof( Point ), HatchCmpFnc );
415
416
87.1k
    if( nPCounter & 1 )
417
0
        nPCounter--;
418
419
87.1k
    if( bMtf )
420
0
    {
421
0
        for( tools::Long i = 0; i < nPCounter; i += 2 )
422
0
            mpMetaFile->AddAction( new MetaLineAction( pPtBuffer[ i ], pPtBuffer[ i + 1 ] ) );
423
0
    }
424
87.1k
    else
425
87.1k
    {
426
1.68M
        for( tools::Long i = 0; i < nPCounter; i += 2 )
427
1.59M
            DrawHatchLine_DrawLine(pPtBuffer[i], pPtBuffer[i+1]);
428
87.1k
    }
429
87.1k
}
430
431
void OutputDevice::DrawHatchLine_DrawLine(const Point& rStartPoint, const Point& rEndPoint)
432
1.59M
{
433
1.59M
    Point aPt1{ImplLogicToDevicePixel(rStartPoint)}, aPt2{ImplLogicToDevicePixel(rEndPoint)};
434
1.59M
    mpGraphics->DrawLine(aPt1.X(), aPt1.Y(), aPt2.X(), aPt2.Y(), *this);
435
1.59M
}
436
437
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */