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/gridprimitive2d.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 <drawinglayer/primitive2d/gridprimitive2d.hxx>
21
#include <drawinglayer/primitive2d/pointarrayprimitive2d.hxx>
22
#include <drawinglayer/primitive2d/markerarrayprimitive2d.hxx>
23
#include <drawinglayer/primitive2d/groupprimitive2d.hxx>
24
#include <drawinglayer/geometry/viewinformation2d.hxx>
25
#include <drawinglayer/primitive2d/drawinglayer_primitivetypes2d.hxx>
26
#include <basegfx/matrix/b2dhommatrixtools.hxx>
27
#include <utility>
28
29
30
using namespace com::sun::star;
31
32
33
namespace drawinglayer::primitive2d
34
{
35
        Primitive2DReference GridPrimitive2D::create2DDecomposition(const geometry::ViewInformation2D& rViewInformation) const
36
0
        {
37
0
            if(!(!rViewInformation.getViewport().isEmpty() && getWidth() > 0.0 && getHeight() > 0.0))
38
0
                return nullptr;
39
40
            // decompose grid matrix to get logic size
41
0
            basegfx::B2DVector aScale, aTranslate;
42
0
            double fRotate, fShearX;
43
0
            getTransform().decompose(aScale, aTranslate, fRotate, fShearX);
44
45
            // create grid matrix which transforms from scaled logic to view
46
0
            basegfx::B2DHomMatrix aRST(basegfx::utils::createShearXRotateTranslateB2DHomMatrix(
47
0
                fShearX, fRotate, aTranslate.getX(), aTranslate.getY()));
48
0
            aRST *= rViewInformation.getObjectToViewTransformation();
49
50
            // get step widths
51
0
            double fStepX(getWidth());
52
0
            double fStepY(getHeight());
53
0
            const double fMinimalStep(10.0);
54
55
            // guarantee a step width of 10.0
56
0
            if(basegfx::fTools::less(fStepX, fMinimalStep))
57
0
            {
58
0
                fStepX = fMinimalStep;
59
0
            }
60
61
0
            if(basegfx::fTools::less(fStepY, fMinimalStep))
62
0
            {
63
0
                fStepY = fMinimalStep;
64
0
            }
65
66
            // get relative distances in view coordinates
67
0
            double fViewStepX((rViewInformation.getObjectToViewTransformation() * basegfx::B2DVector(fStepX, 0.0)).getLength());
68
0
            double fViewStepY((rViewInformation.getObjectToViewTransformation() * basegfx::B2DVector(0.0, fStepY)).getLength());
69
0
            double fSmallStepX(1.0), fViewSmallStepX(1.0), fSmallStepY(1.0), fViewSmallStepY(1.0);
70
0
            sal_uInt32 nSmallStepsX(0), nSmallStepsY(0);
71
72
            // setup subdivisions
73
0
            if(getSubdivisionsX())
74
0
            {
75
0
                fSmallStepX = fStepX / getSubdivisionsX();
76
0
                fViewSmallStepX = fViewStepX / getSubdivisionsX();
77
0
            }
78
79
0
            if(getSubdivisionsY())
80
0
            {
81
0
                fSmallStepY = fStepY / getSubdivisionsY();
82
0
                fViewSmallStepY = fViewStepY / getSubdivisionsY();
83
0
            }
84
85
            // correct step width
86
0
            while(fViewStepX < getSmallestViewDistance())
87
0
            {
88
0
                fViewStepX *= 2.0;
89
0
                fStepX *= 2.0;
90
0
            }
91
92
0
            while(fViewStepY < getSmallestViewDistance())
93
0
            {
94
0
                fViewStepY *= 2.0;
95
0
                fStepY *= 2.0;
96
0
            }
97
98
            // correct small step width
99
0
            if(getSubdivisionsX())
100
0
            {
101
0
                while(fViewSmallStepX < getSmallestSubdivisionViewDistance())
102
0
                {
103
0
                    fViewSmallStepX *= 2.0;
104
0
                    fSmallStepX *= 2.0;
105
0
                }
106
107
0
                nSmallStepsX = static_cast<sal_uInt32>(fStepX / fSmallStepX);
108
0
            }
109
110
0
            if(getSubdivisionsY())
111
0
            {
112
0
                while(fViewSmallStepY < getSmallestSubdivisionViewDistance())
113
0
                {
114
0
                    fViewSmallStepY *= 2.0;
115
0
                    fSmallStepY *= 2.0;
116
0
                }
117
118
0
                nSmallStepsY = static_cast<sal_uInt32>(fStepY / fSmallStepY);
119
0
            }
120
121
            // calculate extended viewport in which grid points may lie at all
122
0
            basegfx::B2DRange aExtendedViewport;
123
124
0
            if(rViewInformation.getDiscreteViewport().isEmpty())
125
0
            {
126
                // not set, use logic size to travel over all potential grid points
127
0
                aExtendedViewport = basegfx::B2DRange(0.0, 0.0, aScale.getX(), aScale.getY());
128
0
            }
129
0
            else
130
0
            {
131
                // transform unit range to discrete view
132
0
                aExtendedViewport = basegfx::B2DRange(0.0, 0.0, 1.0, 1.0);
133
0
                basegfx::B2DHomMatrix aTrans(rViewInformation.getObjectToViewTransformation() * getTransform());
134
0
                aExtendedViewport.transform(aTrans);
135
136
                // intersect with visible part
137
0
                aExtendedViewport.intersect(rViewInformation.getDiscreteViewport());
138
139
0
                if(!aExtendedViewport.isEmpty())
140
0
                {
141
                    // convert back and apply scale
142
0
                    aTrans.invert();
143
0
                    aTrans.scale(aScale.getX(), aScale.getY());
144
0
                    aExtendedViewport.transform(aTrans);
145
146
                    // crop start/end in X/Y to multiples of logical step width
147
0
                    const double fHalfCrossSize((rViewInformation.getInverseObjectToViewTransformation() * basegfx::B2DVector(3.0, 0.0)).getLength());
148
0
                    const double fMinX(floor((aExtendedViewport.getMinX() - fHalfCrossSize) / fStepX) * fStepX);
149
0
                    const double fMaxX(ceil((aExtendedViewport.getMaxX() + fHalfCrossSize) / fStepX) * fStepX);
150
0
                    const double fMinY(floor((aExtendedViewport.getMinY() - fHalfCrossSize) / fStepY) * fStepY);
151
0
                    const double fMaxY(ceil((aExtendedViewport.getMaxY() + fHalfCrossSize) / fStepY) * fStepY);
152
153
                    // put to aExtendedViewport and crop on object logic size
154
0
                    aExtendedViewport = basegfx::B2DRange(
155
0
                        std::max(fMinX, 0.0),
156
0
                        std::max(fMinY, 0.0),
157
0
                        std::min(fMaxX, aScale.getX()),
158
0
                        std::min(fMaxY, aScale.getY()));
159
0
                }
160
0
            }
161
162
0
            if(aExtendedViewport.isEmpty())
163
0
                return nullptr;
164
165
            // prepare point vectors for point and cross markers
166
0
            std::vector< basegfx::B2DPoint > aPositionsPoint;
167
0
            std::vector< basegfx::B2DPoint > aPositionsCross;
168
169
0
            for(double fX(aExtendedViewport.getMinX()); fX < aExtendedViewport.getMaxX(); fX += fStepX)
170
0
            {
171
0
                const bool bXZero(basegfx::fTools::equalZero(fX));
172
173
0
                for(double fY(aExtendedViewport.getMinY()); fY < aExtendedViewport.getMaxY(); fY += fStepY)
174
0
                {
175
0
                    const bool bYZero(basegfx::fTools::equalZero(fY));
176
177
0
                    if(!bXZero && !bYZero)
178
0
                    {
179
                        // get discrete position and test against 3x3 area surrounding it
180
                        // since it's a cross
181
0
                        const double fHalfCrossSize(3.0 * 0.5);
182
0
                        const basegfx::B2DPoint aViewPos(aRST * basegfx::B2DPoint(fX, fY));
183
0
                        const basegfx::B2DRange aDiscreteRangeCross(
184
0
                            aViewPos.getX() - fHalfCrossSize, aViewPos.getY() - fHalfCrossSize,
185
0
                            aViewPos.getX() + fHalfCrossSize, aViewPos.getY() + fHalfCrossSize);
186
187
0
                        if(rViewInformation.getDiscreteViewport().overlaps(aDiscreteRangeCross))
188
0
                        {
189
0
                            const basegfx::B2DPoint aLogicPos(rViewInformation.getInverseObjectToViewTransformation() * aViewPos);
190
0
                            aPositionsCross.push_back(aLogicPos);
191
0
                        }
192
0
                    }
193
194
0
                    if(getSubdivisionsX() && !bYZero)
195
0
                    {
196
0
                        double fF(fX + fSmallStepX);
197
198
0
                        for(sal_uInt32 a(1); a < nSmallStepsX && fF < aExtendedViewport.getMaxX(); a++, fF += fSmallStepX)
199
0
                        {
200
0
                            const basegfx::B2DPoint aViewPos(aRST * basegfx::B2DPoint(fF, fY));
201
202
0
                            if(rViewInformation.getDiscreteViewport().isInside(aViewPos))
203
0
                            {
204
0
                                const basegfx::B2DPoint aLogicPos(rViewInformation.getInverseObjectToViewTransformation() * aViewPos);
205
0
                                aPositionsPoint.push_back(aLogicPos);
206
0
                            }
207
0
                        }
208
0
                    }
209
210
0
                    if(getSubdivisionsY() && !bXZero)
211
0
                    {
212
0
                        double fF(fY + fSmallStepY);
213
214
0
                        for(sal_uInt32 a(1); a < nSmallStepsY && fF < aExtendedViewport.getMaxY(); a++, fF += fSmallStepY)
215
0
                        {
216
0
                            const basegfx::B2DPoint aViewPos(aRST * basegfx::B2DPoint(fX, fF));
217
218
0
                            if(rViewInformation.getDiscreteViewport().isInside(aViewPos))
219
0
                            {
220
0
                                const basegfx::B2DPoint aLogicPos(rViewInformation.getInverseObjectToViewTransformation() * aViewPos);
221
0
                                aPositionsPoint.push_back(aLogicPos);
222
0
                            }
223
0
                        }
224
0
                    }
225
0
                }
226
0
            }
227
228
            // prepare return value
229
0
            const sal_uInt32 nCountPoint(aPositionsPoint.size());
230
0
            const sal_uInt32 nCountCross(aPositionsCross.size());
231
232
            // add PointArrayPrimitive2D if point markers were added
233
0
            Primitive2DContainer aContainer;
234
0
            if(nCountPoint)
235
0
            {
236
0
                aContainer.push_back(new PointArrayPrimitive2D(std::move(aPositionsPoint), getBColor()));
237
0
            }
238
239
            // add MarkerArrayPrimitive2D if cross markers were added
240
0
            if(!nCountCross)
241
0
                return new GroupPrimitive2D(std::move(aContainer));
242
243
0
            if(!getSubdivisionsX() && !getSubdivisionsY())
244
0
            {
245
                // no subdivisions, so fall back to points at grid positions, no need to
246
                // visualize a difference between divisions and sub-divisions
247
0
                aContainer.push_back(new PointArrayPrimitive2D(std::move(aPositionsCross), getBColor()));
248
0
            }
249
0
            else
250
0
            {
251
0
                aContainer.push_back(new MarkerArrayPrimitive2D(std::move(aPositionsCross), getCrossMarker()));
252
0
            }
253
0
            return new GroupPrimitive2D(std::move(aContainer));
254
0
        }
255
256
        GridPrimitive2D::GridPrimitive2D(
257
            basegfx::B2DHomMatrix aTransform,
258
            double fWidth,
259
            double fHeight,
260
            double fSmallestViewDistance,
261
            double fSmallestSubdivisionViewDistance,
262
            sal_uInt32 nSubdivisionsX,
263
            sal_uInt32 nSubdivisionsY,
264
            const basegfx::BColor& rBColor,
265
            const Bitmap& rCrossMarker)
266
0
        :   maTransform(std::move(aTransform)),
267
0
            mfWidth(fWidth),
268
0
            mfHeight(fHeight),
269
0
            mfSmallestViewDistance(fSmallestViewDistance),
270
0
            mfSmallestSubdivisionViewDistance(fSmallestSubdivisionViewDistance),
271
0
            mnSubdivisionsX(nSubdivisionsX),
272
0
            mnSubdivisionsY(nSubdivisionsY),
273
0
            maBColor(rBColor),
274
0
            maCrossMarker(rCrossMarker)
275
0
        {
276
0
        }
277
278
        bool GridPrimitive2D::operator==(const BasePrimitive2D& rPrimitive) const
279
0
        {
280
0
            if(BufferedDecompositionPrimitive2D::operator==(rPrimitive))
281
0
            {
282
0
                const GridPrimitive2D& rCompare = static_cast<const GridPrimitive2D&>(rPrimitive);
283
284
0
                return (getTransform() == rCompare.getTransform()
285
0
                    && getWidth() == rCompare.getWidth()
286
0
                    && getHeight() == rCompare.getHeight()
287
0
                    && getSmallestViewDistance() == rCompare.getSmallestViewDistance()
288
0
                    && getSmallestSubdivisionViewDistance() == rCompare.getSmallestSubdivisionViewDistance()
289
0
                    && getSubdivisionsX() == rCompare.getSubdivisionsX()
290
0
                    && getSubdivisionsY() == rCompare.getSubdivisionsY()
291
0
                    && getBColor() == rCompare.getBColor()
292
0
                    && getCrossMarker() == rCompare.getCrossMarker());
293
0
            }
294
295
0
            return false;
296
0
        }
297
298
        basegfx::B2DRange GridPrimitive2D::getB2DRange(const geometry::ViewInformation2D& rViewInformation) const
299
0
        {
300
            // get object's range
301
0
            basegfx::B2DRange aUnitRange(0.0, 0.0, 1.0, 1.0);
302
0
            aUnitRange.transform(getTransform());
303
304
            // intersect with visible part
305
0
            aUnitRange.intersect(rViewInformation.getViewport());
306
307
0
            return aUnitRange;
308
0
        }
309
310
        void GridPrimitive2D::get2DDecomposition(Primitive2DDecompositionVisitor& rVisitor, const geometry::ViewInformation2D& rViewInformation) const
311
0
        {
312
0
            if(hasBuffered2DDecomposition())
313
0
            {
314
0
                if(maLastViewport != rViewInformation.getViewport() || maLastObjectToViewTransformation != rViewInformation.getObjectToViewTransformation())
315
0
                {
316
                    // conditions of last local decomposition have changed, delete
317
0
                    const_cast< GridPrimitive2D* >(this)->setBuffered2DDecomposition(nullptr);
318
0
                }
319
0
            }
320
321
0
            if(!hasBuffered2DDecomposition())
322
0
            {
323
                // remember ViewRange and ViewTransformation
324
0
                const_cast< GridPrimitive2D* >(this)->maLastObjectToViewTransformation = rViewInformation.getObjectToViewTransformation();
325
0
                const_cast< GridPrimitive2D* >(this)->maLastViewport = rViewInformation.getViewport();
326
0
            }
327
328
            // use parent implementation
329
0
            BufferedDecompositionPrimitive2D::get2DDecomposition(rVisitor, rViewInformation);
330
0
        }
331
332
        // provide unique ID
333
        sal_uInt32 GridPrimitive2D::getPrimitive2DID() const
334
0
        {
335
0
            return PRIMITIVE2D_ID_GRIDPRIMITIVE2D;
336
0
        }
337
338
} // end of namespace
339
340
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */