Coverage Report

Created: 2026-02-14 09:37

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/libreoffice/drawinglayer/source/primitive2d/polygonprimitive2d.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 <basegfx/polygon/b2dpolygontools.hxx>
21
#include <drawinglayer/primitive2d/PolyPolygonHairlinePrimitive2D.hxx>
22
#include <drawinglayer/primitive2d/PolyPolygonColorPrimitive2D.hxx>
23
#include <drawinglayer/primitive2d/PolygonHairlinePrimitive2D.hxx>
24
#include <drawinglayer/primitive2d/PolygonMarkerPrimitive2D.hxx>
25
#include <drawinglayer/primitive2d/PolygonStrokeArrowPrimitive2D.hxx>
26
#include <drawinglayer/primitive2d/PolygonWavePrimitive2D.hxx>
27
#include <drawinglayer/primitive2d/pointarrayprimitive2d.hxx>
28
#include <drawinglayer/primitive2d/drawinglayer_primitivetypes2d.hxx>
29
#include <drawinglayer/primitive2d/groupprimitive2d.hxx>
30
#include <drawinglayer/geometry/viewinformation2d.hxx>
31
#include <basegfx/polygon/b2dlinegeometry.hxx>
32
#include <com/sun/star/drawing/LineCap.hpp>
33
#include <utility>
34
35
using namespace com::sun::star;
36
37
namespace
38
{
39
void implGrowHairline(basegfx::B2DRange& rRange,
40
                      const drawinglayer::geometry::ViewInformation2D& rViewInformation)
41
101
{
42
101
    if (!rRange.isEmpty())
43
101
    {
44
        // Calculate view-dependent hairline width
45
101
        const basegfx::B2DVector aDiscreteSize(
46
101
            rViewInformation.getInverseObjectToViewTransformation() * basegfx::B2DVector(1.0, 0.0));
47
101
        const double fDiscreteHalfLineWidth(aDiscreteSize.getLength() * 0.5);
48
49
101
        if (fDiscreteHalfLineWidth > 0.0)
50
101
        {
51
101
            rRange.grow(fDiscreteHalfLineWidth);
52
101
        }
53
101
    }
54
101
}
55
}
56
57
namespace drawinglayer::primitive2d
58
{
59
PolygonHairlinePrimitive2D::PolygonHairlinePrimitive2D(basegfx::B2DPolygon aPolygon,
60
                                                       const basegfx::BColor& rBColor)
61
301
    : maPolygon(std::move(aPolygon))
62
301
    , maBColor(rBColor)
63
301
{
64
301
}
65
66
bool PolygonHairlinePrimitive2D::operator==(const BasePrimitive2D& rPrimitive) const
67
0
{
68
0
    if (BasePrimitive2D::operator==(rPrimitive))
69
0
    {
70
0
        const PolygonHairlinePrimitive2D& rCompare
71
0
            = static_cast<const PolygonHairlinePrimitive2D&>(rPrimitive);
72
73
0
        return (getB2DPolygon() == rCompare.getB2DPolygon() && getBColor() == rCompare.getBColor());
74
0
    }
75
76
0
    return false;
77
0
}
78
79
basegfx::B2DRange
80
PolygonHairlinePrimitive2D::getB2DRange(const geometry::ViewInformation2D& rViewInformation) const
81
101
{
82
    // this is a hairline, thus the line width is view-dependent. Get range of polygon
83
    // as base size
84
101
    basegfx::B2DRange aRetval(getB2DPolygon().getB2DRange());
85
86
    // Calculate and grow by view-dependent hairline width
87
101
    implGrowHairline(aRetval, rViewInformation);
88
89
    // return range
90
101
    return aRetval;
91
101
}
92
93
// provide unique ID
94
sal_uInt32 PolygonHairlinePrimitive2D::getPrimitive2DID() const
95
301
{
96
301
    return PRIMITIVE2D_ID_POLYGONHAIRLINEPRIMITIVE2D;
97
301
}
98
99
SingleLinePrimitive2D::SingleLinePrimitive2D(const basegfx::B2DPoint& rStart,
100
                                             const basegfx::B2DPoint& rEnd,
101
                                             const basegfx::BColor& rBColor)
102
0
    : BasePrimitive2D()
103
0
    , maStart(rStart)
104
0
    , maEnd(rEnd)
105
0
    , maBColor(rBColor)
106
0
{
107
0
}
108
109
bool SingleLinePrimitive2D::operator==(const BasePrimitive2D& rPrimitive) const
110
0
{
111
0
    if (BasePrimitive2D::operator==(rPrimitive))
112
0
    {
113
0
        const SingleLinePrimitive2D& rCompare(
114
0
            static_cast<const SingleLinePrimitive2D&>(rPrimitive));
115
116
0
        return (getStart() == rCompare.getStart() && getEnd() == rCompare.getEnd()
117
0
                && getBColor() == rCompare.getBColor());
118
0
    }
119
120
0
    return false;
121
0
}
122
123
basegfx::B2DRange
124
SingleLinePrimitive2D::getB2DRange(const geometry::ViewInformation2D& rViewInformation) const
125
0
{
126
0
    basegfx::B2DRange aRetval(getStart(), getEnd());
127
128
    // Calculate and grow by view-dependent hairline width
129
0
    implGrowHairline(aRetval, rViewInformation);
130
131
0
    return aRetval;
132
0
}
133
134
sal_uInt32 SingleLinePrimitive2D::getPrimitive2DID() const
135
0
{
136
0
    return PRIMITIVE2D_ID_SINGLELINEPRIMITIVE2D;
137
0
}
138
139
void SingleLinePrimitive2D::get2DDecomposition(
140
    Primitive2DDecompositionVisitor& rVisitor,
141
    const geometry::ViewInformation2D& /*rViewInformation*/) const
142
0
{
143
0
    if (getStart() == getEnd())
144
0
    {
145
        // single point
146
0
        Primitive2DContainer aSequence = { new PointArrayPrimitive2D(
147
0
            std::vector<basegfx::B2DPoint>{ getStart() }, getBColor()) };
148
0
        rVisitor.visit(aSequence);
149
0
    }
150
0
    else
151
0
    {
152
        // line
153
0
        Primitive2DContainer aSequence = { new PolygonHairlinePrimitive2D(
154
0
            basegfx::B2DPolygon{ getStart(), getEnd() }, getBColor()) };
155
0
        rVisitor.visit(aSequence);
156
0
    }
157
0
}
158
159
LineRectanglePrimitive2D::LineRectanglePrimitive2D(const basegfx::B2DRange& rB2DRange,
160
                                                   const basegfx::BColor& rBColor)
161
0
    : BasePrimitive2D()
162
0
    , maB2DRange(rB2DRange)
163
0
    , maBColor(rBColor)
164
0
{
165
0
}
166
167
bool LineRectanglePrimitive2D::operator==(const BasePrimitive2D& rPrimitive) const
168
0
{
169
0
    if (BasePrimitive2D::operator==(rPrimitive))
170
0
    {
171
0
        const LineRectanglePrimitive2D& rCompare(
172
0
            static_cast<const LineRectanglePrimitive2D&>(rPrimitive));
173
174
0
        return (getB2DRange() == rCompare.getB2DRange() && getBColor() == rCompare.getBColor());
175
0
    }
176
177
0
    return false;
178
0
}
179
180
basegfx::B2DRange
181
LineRectanglePrimitive2D::getB2DRange(const geometry::ViewInformation2D& rViewInformation) const
182
0
{
183
0
    basegfx::B2DRange aRetval(getB2DRange());
184
185
    // Calculate and grow by view-dependent hairline width
186
0
    implGrowHairline(aRetval, rViewInformation);
187
188
0
    return aRetval;
189
0
}
190
191
sal_uInt32 LineRectanglePrimitive2D::getPrimitive2DID() const
192
0
{
193
0
    return PRIMITIVE2D_ID_LINERECTANGLEPRIMITIVE2D;
194
0
}
195
196
void LineRectanglePrimitive2D::get2DDecomposition(
197
    Primitive2DDecompositionVisitor& rVisitor,
198
    const geometry::ViewInformation2D& /*rViewInformation*/) const
199
0
{
200
0
    if (getB2DRange().isEmpty())
201
0
    {
202
        // no geometry, done
203
0
        return;
204
0
    }
205
206
0
    const basegfx::B2DPolygon aPolygon(basegfx::utils::createPolygonFromRect(getB2DRange()));
207
0
    Primitive2DContainer aSequence = { new PolygonHairlinePrimitive2D(aPolygon, getBColor()) };
208
0
    rVisitor.visit(aSequence);
209
0
}
210
211
Primitive2DReference PolygonMarkerPrimitive2D::create2DDecomposition(
212
    const geometry::ViewInformation2D& rViewInformation) const
213
0
{
214
    // calculate logic DashLength
215
0
    const basegfx::B2DVector aDashVector(rViewInformation.getInverseObjectToViewTransformation()
216
0
                                         * basegfx::B2DVector(getDiscreteDashLength(), 0.0));
217
0
    const double fLogicDashLength(aDashVector.getX());
218
219
0
    if (fLogicDashLength > 0.0 && !getRGBColorA().equal(getRGBColorB()))
220
0
    {
221
        // apply dashing; get line and gap snippets
222
0
        std::vector<double> aDash;
223
0
        basegfx::B2DPolyPolygon aDashedPolyPolyA;
224
0
        basegfx::B2DPolyPolygon aDashedPolyPolyB;
225
226
0
        aDash.push_back(fLogicDashLength);
227
0
        aDash.push_back(fLogicDashLength);
228
0
        basegfx::utils::applyLineDashing(getB2DPolygon(), aDash, &aDashedPolyPolyA,
229
0
                                         &aDashedPolyPolyB, 2.0 * fLogicDashLength);
230
231
0
        Primitive2DContainer aContainer;
232
0
        aContainer.push_back(
233
0
            new PolyPolygonHairlinePrimitive2D(std::move(aDashedPolyPolyA), getRGBColorA()));
234
0
        aContainer.push_back(
235
0
            new PolyPolygonHairlinePrimitive2D(std::move(aDashedPolyPolyB), getRGBColorB()));
236
0
        return new GroupPrimitive2D(std::move(aContainer));
237
0
    }
238
0
    else
239
0
    {
240
0
        return new PolygonHairlinePrimitive2D(getB2DPolygon(), getRGBColorA());
241
0
    }
242
0
}
243
244
PolygonMarkerPrimitive2D::PolygonMarkerPrimitive2D(basegfx::B2DPolygon aPolygon,
245
                                                   const basegfx::BColor& rRGBColorA,
246
                                                   const basegfx::BColor& rRGBColorB,
247
                                                   double fDiscreteDashLength)
248
0
    : maPolygon(std::move(aPolygon))
249
0
    , maRGBColorA(rRGBColorA)
250
0
    , maRGBColorB(rRGBColorB)
251
0
    , mfDiscreteDashLength(fDiscreteDashLength)
252
0
{
253
0
}
254
255
bool PolygonMarkerPrimitive2D::operator==(const BasePrimitive2D& rPrimitive) const
256
0
{
257
0
    if (BufferedDecompositionPrimitive2D::operator==(rPrimitive))
258
0
    {
259
0
        const PolygonMarkerPrimitive2D& rCompare
260
0
            = static_cast<const PolygonMarkerPrimitive2D&>(rPrimitive);
261
262
0
        return (getB2DPolygon() == rCompare.getB2DPolygon()
263
0
                && getRGBColorA() == rCompare.getRGBColorA()
264
0
                && getRGBColorB() == rCompare.getRGBColorB()
265
0
                && getDiscreteDashLength() == rCompare.getDiscreteDashLength());
266
0
    }
267
268
0
    return false;
269
0
}
270
271
basegfx::B2DRange
272
PolygonMarkerPrimitive2D::getB2DRange(const geometry::ViewInformation2D& rViewInformation) const
273
0
{
274
    // this is a hairline, thus the line width is view-dependent. Get range of polygon
275
    // as base size
276
0
    basegfx::B2DRange aRetval(getB2DPolygon().getB2DRange());
277
278
0
    if (!aRetval.isEmpty())
279
0
    {
280
        // Calculate view-dependent hairline width
281
0
        const basegfx::B2DVector aDiscreteSize(
282
0
            rViewInformation.getInverseObjectToViewTransformation() * basegfx::B2DVector(1.0, 0.0));
283
0
        const double fDiscreteHalfLineWidth(aDiscreteSize.getLength() * 0.5);
284
285
0
        if (fDiscreteHalfLineWidth > 0.0)
286
0
        {
287
0
            aRetval.grow(fDiscreteHalfLineWidth);
288
0
        }
289
0
    }
290
291
    // return range
292
0
    return aRetval;
293
0
}
294
295
void PolygonMarkerPrimitive2D::get2DDecomposition(
296
    Primitive2DDecompositionVisitor& rVisitor,
297
    const geometry::ViewInformation2D& rViewInformation) const
298
0
{
299
0
    bool bNeedNewDecomposition(false);
300
301
0
    if (hasBuffered2DDecomposition())
302
0
    {
303
0
        if (rViewInformation.getInverseObjectToViewTransformation()
304
0
            != maLastInverseObjectToViewTransformation)
305
0
        {
306
0
            bNeedNewDecomposition = true;
307
0
        }
308
0
    }
309
310
0
    if (bNeedNewDecomposition)
311
0
    {
312
        // conditions of last local decomposition have changed, delete
313
0
        const_cast<PolygonMarkerPrimitive2D*>(this)->setBuffered2DDecomposition(nullptr);
314
0
    }
315
316
0
    if (!hasBuffered2DDecomposition())
317
0
    {
318
        // remember last used InverseObjectToViewTransformation
319
0
        PolygonMarkerPrimitive2D* pThat = const_cast<PolygonMarkerPrimitive2D*>(this);
320
0
        pThat->maLastInverseObjectToViewTransformation
321
0
            = rViewInformation.getInverseObjectToViewTransformation();
322
0
    }
323
324
    // use parent implementation
325
0
    BufferedDecompositionPrimitive2D::get2DDecomposition(rVisitor, rViewInformation);
326
0
}
327
328
// provide unique ID
329
sal_uInt32 PolygonMarkerPrimitive2D::getPrimitive2DID() const
330
0
{
331
0
    return PRIMITIVE2D_ID_POLYGONMARKERPRIMITIVE2D;
332
0
}
333
334
} // end of namespace
335
336
namespace drawinglayer::primitive2d
337
{
338
Primitive2DReference PolygonStrokePrimitive2D::create2DDecomposition(
339
    const geometry::ViewInformation2D& /*rViewInformation*/) const
340
199
{
341
199
    if (!getB2DPolygon().count())
342
0
        return nullptr;
343
344
    // #i102241# try to simplify before usage
345
199
    const basegfx::B2DPolygon aB2DPolygon(basegfx::utils::simplifyCurveSegments(getB2DPolygon()));
346
199
    basegfx::B2DPolyPolygon aHairLinePolyPolygon;
347
348
199
    if (getStrokeAttribute().isDefault() || 0.0 == getStrokeAttribute().getFullDotDashLen())
349
199
    {
350
        // no line dashing, just copy
351
199
        aHairLinePolyPolygon.append(aB2DPolygon);
352
199
    }
353
0
    else
354
0
    {
355
        // apply LineStyle
356
0
        basegfx::utils::applyLineDashing(aB2DPolygon, getStrokeAttribute().getDotDashArray(),
357
0
                                         &aHairLinePolyPolygon, nullptr,
358
0
                                         getStrokeAttribute().getFullDotDashLen());
359
0
    }
360
361
199
    const sal_uInt32 nCount(aHairLinePolyPolygon.count());
362
363
199
    if (!getLineAttribute().isDefault() && getLineAttribute().getWidth())
364
3
    {
365
        // create fat line data
366
3
        const double fHalfLineWidth(getLineAttribute().getWidth() / 2.0);
367
3
        const basegfx::B2DLineJoin aLineJoin(getLineAttribute().getLineJoin());
368
3
        const css::drawing::LineCap aLineCap(getLineAttribute().getLineCap());
369
3
        basegfx::B2DPolyPolygon aAreaPolyPolygon;
370
3
        const double fMiterMinimumAngle(getLineAttribute().getMiterMinimumAngle());
371
372
6
        for (sal_uInt32 a(0); a < nCount; a++)
373
3
        {
374
            // New version of createAreaGeometry; now creates bezier polygons
375
3
            aAreaPolyPolygon.append(basegfx::utils::createAreaGeometry(
376
3
                aHairLinePolyPolygon.getB2DPolygon(a), fHalfLineWidth, aLineJoin, aLineCap,
377
3
                basegfx::deg2rad(12.5) /* default fMaxAllowedAngle*/,
378
3
                0.4 /* default fMaxPartOfEdge*/, fMiterMinimumAngle));
379
3
        }
380
381
        // create primitive
382
3
        Primitive2DContainer aContainer;
383
6
        for (sal_uInt32 b(0); b < aAreaPolyPolygon.count(); b++)
384
3
        {
385
            // put into single polyPolygon primitives to make clear that this is NOT meant
386
            // to be painted as a single tools::PolyPolygon (XORed as fill rule). Alternatively, a
387
            // melting process may be used here one day.
388
3
            basegfx::B2DPolyPolygon aNewPolyPolygon(aAreaPolyPolygon.getB2DPolygon(b));
389
3
            const basegfx::BColor aColor(getLineAttribute().getColor());
390
3
            aContainer.push_back(
391
3
                new PolyPolygonColorPrimitive2D(std::move(aNewPolyPolygon), aColor));
392
3
        }
393
3
        return new GroupPrimitive2D(std::move(aContainer));
394
3
    }
395
196
    else
396
196
    {
397
196
        return new PolyPolygonHairlinePrimitive2D(std::move(aHairLinePolyPolygon),
398
196
                                                  getLineAttribute().getColor());
399
196
    }
400
199
}
401
402
PolygonStrokePrimitive2D::PolygonStrokePrimitive2D(basegfx::B2DPolygon aPolygon,
403
                                                   const attribute::LineAttribute& rLineAttribute,
404
                                                   attribute::StrokeAttribute aStrokeAttribute)
405
4.31k
    : maPolygon(std::move(aPolygon))
406
4.31k
    , maLineAttribute(rLineAttribute)
407
4.31k
    , maStrokeAttribute(std::move(aStrokeAttribute))
408
4.31k
    , maBufferedRange()
409
4.31k
{
410
    // MM01: keep these - these are no curve-decompposers but just checks
411
    // simplify curve segments: moved here to not need to use it
412
    // at VclPixelProcessor2D::tryDrawPolygonStrokePrimitive2DDirect
413
4.31k
    maPolygon = basegfx::utils::simplifyCurveSegments(maPolygon);
414
4.31k
}
415
416
PolygonStrokePrimitive2D::PolygonStrokePrimitive2D(basegfx::B2DPolygon aPolygon,
417
                                                   const attribute::LineAttribute& rLineAttribute)
418
4
    : maPolygon(std::move(aPolygon))
419
4
    , maLineAttribute(rLineAttribute)
420
4
    , maBufferedRange()
421
4
{
422
    // MM01: keep these - these are no curve-decompposers but just checks
423
    // simplify curve segments: moved here to not need to use it
424
    // at VclPixelProcessor2D::tryDrawPolygonStrokePrimitive2DDirect
425
4
    maPolygon = basegfx::utils::simplifyCurveSegments(maPolygon);
426
4
}
427
428
bool PolygonStrokePrimitive2D::operator==(const BasePrimitive2D& rPrimitive) const
429
0
{
430
0
    if (BufferedDecompositionPrimitive2D::operator==(rPrimitive))
431
0
    {
432
0
        const PolygonStrokePrimitive2D& rCompare
433
0
            = static_cast<const PolygonStrokePrimitive2D&>(rPrimitive);
434
435
0
        return (getB2DPolygon() == rCompare.getB2DPolygon()
436
0
                && getLineAttribute() == rCompare.getLineAttribute()
437
0
                && getStrokeAttribute() == rCompare.getStrokeAttribute());
438
0
    }
439
440
0
    return false;
441
0
}
442
443
basegfx::B2DRange
444
PolygonStrokePrimitive2D::getB2DRange(const geometry::ViewInformation2D& rViewInformation) const
445
611
{
446
611
    if (!maBufferedRange.isEmpty())
447
144
    {
448
        // use the view-independent, buffered B2DRange
449
144
        return maBufferedRange;
450
144
    }
451
452
467
    if (getLineAttribute().getWidth())
453
75
    {
454
75
        bool bUseDecomposition(false);
455
456
75
        if (basegfx::B2DLineJoin::Miter == getLineAttribute().getLineJoin())
457
3
        {
458
            // if line is mitered, use parent call since mitered line
459
            // geometry may use more space than the geometry grown by half line width
460
3
            bUseDecomposition = true;
461
3
        }
462
463
75
        if (!bUseDecomposition && css::drawing::LineCap_SQUARE == getLineAttribute().getLineCap())
464
0
        {
465
            // when drawing::LineCap_SQUARE is used the below method to grow the polygon
466
            // range by half line width will not work, so use decomposition. Interestingly,
467
            // the grow method below works perfectly for LineCap_ROUND since the grow is in
468
            // all directions and the rounded cap needs the same grow in all directions independent
469
            // from its orientation. Unfortunately this is not the case for drawing::LineCap_SQUARE
470
471
            // NOTE: I thought about using [sqrt(2) * 0.5] a a factor for LineCap_SQUARE and not
472
            // set bUseDecomposition. I even tried that it works. Then an auto-test failing showed
473
            // not only that view-dependent stuff needs to be considered (what is done for the
474
            // hairline case below), *BUT* also e.g. conversions to PNG/exports use the B2DRange
475
            // of the geometry, so:
476
            // - expanding by 1/2 LineWidth is OK for rounded
477
            // - expanding by more (like sqrt(2) * 0.5 * LineWidth) immediately extends the size
478
            //   of e.g. geometry converted to PNG, plus many similar cases that cannot be thought
479
            //   of in advance.
480
            // This means that converting those thought-experiment examples in (4) will and do lead
481
            // to bigger e.g. Bitmap conversion(s), not avoiding but painting the free space. That
482
            // could only be done by correctly and fully decomposing the geometry, including
483
            // stroke, and accepting the cost...
484
0
            bUseDecomposition = true;
485
0
        }
486
487
75
        if (bUseDecomposition)
488
3
        {
489
            // get correct range by using the decomposition fallback, reasons see above cases
490
491
            // It is not a good idea to temporarily (re)set the PolygonStrokePrimitive2D
492
            // to default. While it is understandable to use the speed advantage, it is
493
            // bad for quite some reasons:
494
            //
495
            // (1) As described in include/drawinglayer/primitive2d/baseprimitive2d.hxx
496
            //     a Primitive is "noncopyable to make clear that a primitive is a read-only
497
            //     instance and copying or changing values is not intended". This is the base
498
            //     assumption for many decisions for Primitive handling.
499
            // (2) For example, that the decomposition is *always* re-usable. It cannot change
500
            //     and is correct when it already exists since the values the decomposition is
501
            //     based on cannot change.
502
            // (3) If this *is* done (like it was here) and the Primitive is derived from
503
            //     BufferedDecompositionPrimitive2D and thus buffers it's decomposition,
504
            //     the risk is that in this case the *wrong* decomposition will be used by
505
            //     other PrimitiveProcessors. Maybe not by the VclPixelProcessor2D/VclProcessor2D
506
            //     since it handles this primitive directly - not even sure for all cases.
507
            //     Sooner or later another PrimitiveProcessor will re-use this wrong temporary
508
            //     decomposition, and as an error, a non-stroked line will be painted/used.
509
            // (4) The B2DRange is not strictly defined as minimal bound for the geometry,
510
            //     but it should be as small/tight as possible. Making it larger risks more
511
            //     area to be invalidated (repaint) and processed (all geometric stuff,l may
512
            //     include future and existing exports to other formats which are or will be
513
            //     implemented as PrimitiveProcessor). It is easy to imagine cases with much
514
            //     too large B2DRange - a line with a pattern that would solve to a single
515
            //     small start-rectangle and rest is empty, or a circle with a stroke that
516
            //     makes only a quarter of it visible.
517
            //
518
            // The reason to do this is speed, what is a good argument. But speed should
519
            // only be used if the pair of [correctness/speed] does not sacrifice the correctness
520
            // over the speed.
521
            // Luckily there are alternatives to solve this and to keep [correctness/speed]
522
            // valid:
523
            //
524
            // (a) Reset the temporary decomposition after having const-casted and
525
            //     changed maStrokeAttribute.
526
            //     Disadvantage: More const-cast hacks, plus this temporary decomposition
527
            //     will be potentially done repeatedly (every time
528
            //     PolygonStrokePrimitive2D::getB2DRange is called)
529
            // (b) Use a temporary, local PolygonStrokePrimitive2D here, with neutral
530
            //     PolygonStrokePrimitive2D and call ::getB2DRange() at it. That way
531
            //     the buffered decomposition will not be harmed.
532
            //     Disadvantage: Same as (a), decomposition will be potentially done repeatedly
533
            // (c) Use a temporary, local PolygonStrokePrimitive2D and buffer B2DRange
534
            //     locally for this Primitive. Due to (1)/(2) this cannot change, so
535
            //     when calculated once it is totally legal to use it.
536
            //
537
            // Thus here I would use (c): It accepts the disadvantages of (4) over speed, but
538
            // avoids the errors/problems from (1-4).
539
            // Additional argument for this: The hairline case below *also* uses the full
540
            // B2DRange of the polygon, ignoring an evtl. stroke, so (4) applies
541
3
            if (!getStrokeAttribute().isDefault())
542
3
            {
543
                // only do this if StrokeAttribute is used, else recursion may happen (!)
544
3
                const rtl::Reference<primitive2d::PolygonStrokePrimitive2D>
545
3
                    aTemporaryPrimitiveWithoutStroke(new primitive2d::PolygonStrokePrimitive2D(
546
3
                        getB2DPolygon(), getLineAttribute()));
547
3
                maBufferedRange
548
3
                    = aTemporaryPrimitiveWithoutStroke
549
3
                          ->BufferedDecompositionPrimitive2D::getB2DRange(rViewInformation);
550
3
            }
551
0
            else
552
0
            {
553
                // fallback to normal decompose, that result can be used for visualization
554
                // later, too. Still buffer B2DRange in maBufferedRange, so it needs to be
555
                // merged into one B2DRange only once
556
0
                maBufferedRange = BufferedDecompositionPrimitive2D::getB2DRange(rViewInformation);
557
0
            }
558
3
        }
559
72
        else
560
72
        {
561
            // for all other B2DLINEJOIN_* get the range from the base geometry
562
            // and expand by half the line width.
563
72
            maBufferedRange = getB2DPolygon().getB2DRange();
564
72
            maBufferedRange.grow(getLineAttribute().getWidth() * 0.5);
565
72
        }
566
567
75
        return maBufferedRange;
568
75
    }
569
570
    // It is a hairline, thus the line width is view-dependent. Get range of polygon
571
    // as base size.
572
    // CAUTION: Since a hairline *is* view-dependent,
573
    // - either use maBufferedRange, additionally remember view-dependent
574
    //   factor & reset if that changes
575
    // - or do not buffer for hairline -> not really needed, the range is buffered
576
    //   in the B2DPolygon, no decomposition is needed and a simple grow is cheap
577
392
    basegfx::B2DRange aHairlineRange = getB2DPolygon().getB2DRange();
578
579
392
    if (!aHairlineRange.isEmpty())
580
392
    {
581
        // Calculate view-dependent hairline width
582
392
        const basegfx::B2DVector aDiscreteSize(
583
392
            rViewInformation.getInverseObjectToViewTransformation() * basegfx::B2DVector(1.0, 0.0));
584
392
        const double fDiscreteHalfLineWidth(aDiscreteSize.getLength() * 0.5);
585
586
392
        if (fDiscreteHalfLineWidth > 0.0)
587
392
        {
588
392
            aHairlineRange.grow(fDiscreteHalfLineWidth);
589
392
        }
590
392
    }
591
592
392
    return aHairlineRange;
593
467
}
594
595
// provide unique ID
596
sal_uInt32 PolygonStrokePrimitive2D::getPrimitive2DID() const
597
4.38k
{
598
4.38k
    return PRIMITIVE2D_ID_POLYGONSTROKEPRIMITIVE2D;
599
4.38k
}
600
601
Primitive2DReference PolygonWavePrimitive2D::create2DDecomposition(
602
    const geometry::ViewInformation2D& /*rViewInformation*/) const
603
0
{
604
0
    if (!getB2DPolygon().count())
605
0
        return nullptr;
606
607
0
    const bool bHasWidth(!basegfx::fTools::equalZero(getWaveWidth()));
608
0
    const bool bHasHeight(!basegfx::fTools::equalZero(getWaveHeight()));
609
610
0
    if (bHasWidth && bHasHeight)
611
0
    {
612
        // create waveline curve
613
0
        basegfx::B2DPolygon aWaveline(
614
0
            basegfx::utils::createWaveline(getB2DPolygon(), getWaveWidth(), getWaveHeight()));
615
0
        return new PolygonStrokePrimitive2D(std::move(aWaveline), getLineAttribute(),
616
0
                                            getStrokeAttribute());
617
0
    }
618
0
    else
619
0
    {
620
        // flat waveline, decompose to simple line primitive
621
0
        return new PolygonStrokePrimitive2D(getB2DPolygon(), getLineAttribute(),
622
0
                                            getStrokeAttribute());
623
0
    }
624
0
}
625
626
PolygonWavePrimitive2D::PolygonWavePrimitive2D(const basegfx::B2DPolygon& rPolygon,
627
                                               const attribute::LineAttribute& rLineAttribute,
628
                                               const attribute::StrokeAttribute& rStrokeAttribute,
629
                                               double fWaveWidth, double fWaveHeight)
630
0
    : PolygonStrokePrimitive2D(rPolygon, rLineAttribute, rStrokeAttribute)
631
0
    , mfWaveWidth(fWaveWidth)
632
0
    , mfWaveHeight(fWaveHeight)
633
0
{
634
0
    if (mfWaveWidth < 0.0)
635
0
    {
636
0
        mfWaveWidth = 0.0;
637
0
    }
638
639
0
    if (mfWaveHeight < 0.0)
640
0
    {
641
0
        mfWaveHeight = 0.0;
642
0
    }
643
0
}
644
645
PolygonWavePrimitive2D::PolygonWavePrimitive2D(const basegfx::B2DPolygon& rPolygon,
646
                                               const attribute::LineAttribute& rLineAttribute,
647
                                               double fWaveWidth, double fWaveHeight)
648
0
    : PolygonStrokePrimitive2D(rPolygon, rLineAttribute)
649
0
    , mfWaveWidth(fWaveWidth)
650
0
    , mfWaveHeight(fWaveHeight)
651
0
{
652
0
    if (mfWaveWidth < 0.0)
653
0
    {
654
0
        mfWaveWidth = 0.0;
655
0
    }
656
657
0
    if (mfWaveHeight < 0.0)
658
0
    {
659
0
        mfWaveHeight = 0.0;
660
0
    }
661
0
}
662
663
bool PolygonWavePrimitive2D::operator==(const BasePrimitive2D& rPrimitive) const
664
0
{
665
0
    if (PolygonStrokePrimitive2D::operator==(rPrimitive))
666
0
    {
667
0
        const PolygonWavePrimitive2D& rCompare
668
0
            = static_cast<const PolygonWavePrimitive2D&>(rPrimitive);
669
670
0
        return (getWaveWidth() == rCompare.getWaveWidth()
671
0
                && getWaveHeight() == rCompare.getWaveHeight());
672
0
    }
673
674
0
    return false;
675
0
}
676
677
basegfx::B2DRange
678
PolygonWavePrimitive2D::getB2DRange(const geometry::ViewInformation2D& rViewInformation) const
679
0
{
680
    // get range of parent
681
0
    basegfx::B2DRange aRetval(PolygonStrokePrimitive2D::getB2DRange(rViewInformation));
682
683
    // if WaveHeight, grow by it
684
0
    if (getWaveHeight() > 0.0)
685
0
    {
686
0
        aRetval.grow(getWaveHeight());
687
0
    }
688
689
    // if line width, grow by it
690
0
    if (getLineAttribute().getWidth() > 0.0)
691
0
    {
692
0
        aRetval.grow(getLineAttribute().getWidth() * 0.5);
693
0
    }
694
695
0
    return aRetval;
696
0
}
697
698
// provide unique ID
699
sal_uInt32 PolygonWavePrimitive2D::getPrimitive2DID() const
700
0
{
701
0
    return PRIMITIVE2D_ID_POLYGONWAVEPRIMITIVE2D;
702
0
}
703
704
Primitive2DReference PolygonStrokeArrowPrimitive2D::create2DDecomposition(
705
    const geometry::ViewInformation2D& /*rViewInformation*/) const
706
0
{
707
    // copy local polygon, it may be changed
708
0
    basegfx::B2DPolygon aLocalPolygon(getB2DPolygon());
709
0
    aLocalPolygon.removeDoublePoints();
710
0
    basegfx::B2DPolyPolygon aArrowA;
711
0
    basegfx::B2DPolyPolygon aArrowB;
712
713
0
    if (!aLocalPolygon.isClosed() && aLocalPolygon.count() > 1)
714
0
    {
715
        // apply arrows
716
0
        const double fPolyLength(basegfx::utils::getLength(aLocalPolygon));
717
0
        double fStart(0.0);
718
0
        double fEnd(0.0);
719
0
        double fStartOverlap(0.0);
720
0
        double fEndOverlap(0.0);
721
722
0
        if (!getStart().isDefault() && getStart().isActive())
723
0
        {
724
            // create start arrow primitive and consume
725
0
            aArrowA = basegfx::utils::createAreaGeometryForLineStartEnd(
726
0
                aLocalPolygon, getStart().getB2DPolyPolygon(), true, getStart().getWidth(),
727
0
                fPolyLength, getStart().isCentered() ? 0.5 : 0.0, &fStart);
728
729
            // create some overlapping, compromise between straight and peaked markers
730
            // for marker width 0.3cm and marker line width 0.02cm
731
0
            fStartOverlap = getStart().getWidth() / 15.0;
732
0
        }
733
734
0
        if (!getEnd().isDefault() && getEnd().isActive())
735
0
        {
736
            // create end arrow primitive and consume
737
0
            aArrowB = basegfx::utils::createAreaGeometryForLineStartEnd(
738
0
                aLocalPolygon, getEnd().getB2DPolyPolygon(), false, getEnd().getWidth(),
739
0
                fPolyLength, getEnd().isCentered() ? 0.5 : 0.0, &fEnd);
740
741
            // create some overlapping
742
0
            fEndOverlap = getEnd().getWidth() / 15.0;
743
0
        }
744
745
0
        if (0.0 != fStart || 0.0 != fEnd)
746
0
        {
747
            // build new poly, consume something from old poly
748
0
            aLocalPolygon
749
0
                = basegfx::utils::getSnippetAbsolute(aLocalPolygon, fStart - fStartOverlap,
750
0
                                                     fPolyLength - fEnd + fEndOverlap, fPolyLength);
751
0
        }
752
0
    }
753
754
    // add shaft
755
0
    Primitive2DContainer aContainer;
756
0
    aContainer.push_back(new PolygonStrokePrimitive2D(std::move(aLocalPolygon), getLineAttribute(),
757
0
                                                      getStrokeAttribute()));
758
759
0
    if (aArrowA.count())
760
0
    {
761
0
        aContainer.push_back(
762
0
            new PolyPolygonColorPrimitive2D(std::move(aArrowA), getLineAttribute().getColor()));
763
0
    }
764
765
0
    if (aArrowB.count())
766
0
    {
767
0
        aContainer.push_back(
768
0
            new PolyPolygonColorPrimitive2D(std::move(aArrowB), getLineAttribute().getColor()));
769
0
    }
770
0
    return new GroupPrimitive2D(std::move(aContainer));
771
0
}
772
773
PolygonStrokeArrowPrimitive2D::PolygonStrokeArrowPrimitive2D(
774
    const basegfx::B2DPolygon& rPolygon, const attribute::LineAttribute& rLineAttribute,
775
    const attribute::StrokeAttribute& rStrokeAttribute,
776
    const attribute::LineStartEndAttribute& rStart, const attribute::LineStartEndAttribute& rEnd)
777
0
    : PolygonStrokePrimitive2D(rPolygon, rLineAttribute, rStrokeAttribute)
778
0
    , maStart(rStart)
779
0
    , maEnd(rEnd)
780
0
{
781
0
}
782
783
PolygonStrokeArrowPrimitive2D::PolygonStrokeArrowPrimitive2D(
784
    const basegfx::B2DPolygon& rPolygon, const attribute::LineAttribute& rLineAttribute,
785
    const attribute::LineStartEndAttribute& rStart, const attribute::LineStartEndAttribute& rEnd)
786
0
    : PolygonStrokePrimitive2D(rPolygon, rLineAttribute)
787
0
    , maStart(rStart)
788
0
    , maEnd(rEnd)
789
0
{
790
0
}
791
792
bool PolygonStrokeArrowPrimitive2D::operator==(const BasePrimitive2D& rPrimitive) const
793
0
{
794
0
    if (PolygonStrokePrimitive2D::operator==(rPrimitive))
795
0
    {
796
0
        const PolygonStrokeArrowPrimitive2D& rCompare
797
0
            = static_cast<const PolygonStrokeArrowPrimitive2D&>(rPrimitive);
798
799
0
        return (getStart() == rCompare.getStart() && getEnd() == rCompare.getEnd());
800
0
    }
801
802
0
    return false;
803
0
}
804
805
basegfx::B2DRange PolygonStrokeArrowPrimitive2D::getB2DRange(
806
    const geometry::ViewInformation2D& rViewInformation) const
807
0
{
808
0
    if (getStart().isActive() || getEnd().isActive())
809
0
    {
810
        // use decomposition when line start/end is used
811
0
        return BufferedDecompositionPrimitive2D::getB2DRange(rViewInformation);
812
0
    }
813
0
    else
814
0
    {
815
        // get range from parent
816
0
        return PolygonStrokePrimitive2D::getB2DRange(rViewInformation);
817
0
    }
818
0
}
819
820
// provide unique ID
821
sal_uInt32 PolygonStrokeArrowPrimitive2D::getPrimitive2DID() const
822
0
{
823
0
    return PRIMITIVE2D_ID_POLYGONSTROKEARROWPRIMITIVE2D;
824
0
}
825
826
} // end of namespace
827
828
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */