Coverage Report

Created: 2026-11-06 11:29

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/libreoffice/drawinglayer/source/processor2d/hittestprocessor2d.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/processor2d/hittestprocessor2d.hxx>
21
#include <drawinglayer/primitive2d/drawinglayer_primitivetypes2d.hxx>
22
#include <drawinglayer/primitive2d/transformprimitive2d.hxx>
23
#include <drawinglayer/primitive2d/PolygonHairlinePrimitive2D.hxx>
24
#include <drawinglayer/primitive2d/PolygonMarkerPrimitive2D.hxx>
25
#include <drawinglayer/primitive2d/PolygonWavePrimitive2D.hxx>
26
#include <drawinglayer/primitive2d/PolyPolygonColorPrimitive2D.hxx>
27
#include <drawinglayer/primitive2d/BitmapAlphaPrimitive2D.hxx>
28
#include <basegfx/polygon/b2dpolygontools.hxx>
29
#include <basegfx/polygon/b2dpolypolygontools.hxx>
30
#include <basegfx/range/b3drange.hxx>
31
#include <drawinglayer/primitive2d/transparenceprimitive2d.hxx>
32
#include <drawinglayer/primitive2d/maskprimitive2d.hxx>
33
#include <drawinglayer/primitive2d/sceneprimitive2d.hxx>
34
#include <drawinglayer/primitive2d/pointarrayprimitive2d.hxx>
35
#include <basegfx/matrix/b3dhommatrix.hxx>
36
#include <drawinglayer/processor3d/cutfindprocessor3d.hxx>
37
#include <drawinglayer/primitive2d/hiddengeometryprimitive2d.hxx>
38
#include <drawinglayer/primitive2d/bitmapprimitive2d.hxx>
39
#include <comphelper/lok.hxx>
40
#include <tools/color.hxx>
41
42
namespace drawinglayer::processor2d
43
{
44
        HitTestProcessor2D::HitTestProcessor2D(const geometry::ViewInformation2D& rViewInformation,
45
            const basegfx::B2DPoint& rLogicHitPosition,
46
            const basegfx::B2DVector& rLogicHitTolerancePerAxis,
47
            bool bHitTextOnly)
48
0
        :   BaseProcessor2D(rViewInformation),
49
0
            maDiscreteHitTolerancePerAxis(rLogicHitTolerancePerAxis),
50
0
            mbCollectHitStack(false),
51
0
            mbHit(false),
52
0
            mbHitTextOnly(bHitTextOnly)
53
0
        {
54
            // ensure input parameters for hit tolerance is >= 0.0
55
0
            if (maDiscreteHitTolerancePerAxis.getX() < 0.0)
56
0
                maDiscreteHitTolerancePerAxis.setX(0.0);
57
0
            if (maDiscreteHitTolerancePerAxis.getY() < 0.0)
58
0
                maDiscreteHitTolerancePerAxis.setY(0.0);
59
60
0
            if (!maDiscreteHitTolerancePerAxis.equalZero())
61
0
            {
62
                // generate discrete hit tolerance
63
0
                maDiscreteHitTolerancePerAxis
64
0
                    = getViewInformation2D().getObjectToViewTransformation() * rLogicHitTolerancePerAxis;
65
0
            }
66
67
            // generate discrete hit position
68
0
            maDiscreteHitPosition = getViewInformation2D().getObjectToViewTransformation() * rLogicHitPosition;
69
0
        }
70
71
        HitTestProcessor2D::~HitTestProcessor2D()
72
0
        {
73
0
        }
74
75
        bool HitTestProcessor2D::checkHairlineHitWithTolerance(
76
            const basegfx::B2DPolygon& rPolygon,
77
            const basegfx::B2DVector& rDiscreteHitTolerancePerAxis) const
78
0
        {
79
0
            basegfx::B2DPolygon aLocalPolygon(rPolygon);
80
0
            aLocalPolygon.transform(getViewInformation2D().getObjectToViewTransformation());
81
82
            // get discrete range
83
0
            basegfx::B2DRange aPolygonRange(aLocalPolygon.getB2DRange());
84
85
0
            if(rDiscreteHitTolerancePerAxis.getX() > 0 || rDiscreteHitTolerancePerAxis.getY() > 0)
86
0
            {
87
0
                aPolygonRange.grow(rDiscreteHitTolerancePerAxis);
88
0
            }
89
90
            // do rough range test first
91
0
            if(aPolygonRange.isInside(getDiscreteHitPosition()))
92
0
            {
93
                // check if a polygon edge is hit
94
0
                return basegfx::utils::isInEpsilonRange(
95
0
                    aLocalPolygon,
96
0
                    getDiscreteHitPosition(),
97
0
                    std::max(rDiscreteHitTolerancePerAxis.getX(), rDiscreteHitTolerancePerAxis.getY()));
98
0
            }
99
100
0
            return false;
101
0
        }
102
103
        bool HitTestProcessor2D::checkFillHitWithTolerance(
104
            const basegfx::B2DPolyPolygon& rPolyPolygon,
105
            const basegfx::B2DVector& rDiscreteHitTolerancePerAxis1) const
106
0
        {
107
            // cheaper to transform hit tolerance and position than to transform potentially complex polypolygon
108
0
            const basegfx::B2DHomMatrix& rInvMatrix = getViewInformation2D().getInverseObjectToViewTransformation();
109
0
            basegfx::B2DVector aDiscreteHitTolerancePerAxis2
110
0
                = rInvMatrix * rDiscreteHitTolerancePerAxis1;
111
0
            basegfx::B2DPoint aHitPosition
112
0
                = rInvMatrix * getDiscreteHitPosition();
113
114
0
            basegfx::B2DPolyPolygon aLocalPolyPolygon(rPolyPolygon);
115
116
            // get discrete range
117
0
            basegfx::B2DRange aPolygonRange(aLocalPolyPolygon.getB2DRange());
118
119
0
            const bool bDiscreteHitToleranceUsed(aDiscreteHitTolerancePerAxis2.getX() > 0
120
0
                                                 || aDiscreteHitTolerancePerAxis2.getY() > 0);
121
122
0
            if (bDiscreteHitToleranceUsed)
123
0
            {
124
0
                aPolygonRange.grow(aDiscreteHitTolerancePerAxis2);
125
0
            }
126
127
            // do rough range test first
128
0
            if(!aPolygonRange.isInside(aHitPosition))
129
0
                return false;
130
131
            // if a HitTolerance is given, check for polygon edge hit in epsilon first
132
0
            if(bDiscreteHitToleranceUsed &&
133
0
                basegfx::utils::isInEpsilonRange(
134
0
                    aLocalPolyPolygon,
135
0
                    aHitPosition,
136
0
                    std::max(aDiscreteHitTolerancePerAxis2.getX(), aDiscreteHitTolerancePerAxis2.getY())))
137
0
            {
138
0
                return true;
139
0
            }
140
141
            // check for hit in filled polyPolygon
142
0
            return basegfx::utils::isInside(
143
0
                aLocalPolyPolygon,
144
0
                aHitPosition,
145
0
                true);
146
0
        }
147
148
        void HitTestProcessor2D::checkBitmapHit(basegfx::B2DRange aRange, const Bitmap& rBitmap, const basegfx::B2DHomMatrix& rTransform)
149
0
        {
150
0
            if(!getHitTextOnly())
151
0
            {
152
                // The recently added Bitmap::GetTransparency() makes it easy to extend
153
                // the BitmapPrimitive2D HitTest to take the contained Bitmap and it's
154
                // transparency into account
155
0
                if(!aRange.isEmpty())
156
0
                {
157
0
                    const Size aSizePixel(rBitmap.GetSizePixel());
158
159
                    // When tiled rendering, don't bother with the pixel size of the candidate.
160
0
                    if(aSizePixel.Width() && aSizePixel.Height() && !comphelper::LibreOfficeKit::isActive())
161
0
                    {
162
0
                        basegfx::B2DHomMatrix aBackTransform(
163
0
                            getViewInformation2D().getObjectToViewTransformation() *
164
0
                            rTransform);
165
0
                        aBackTransform.invert();
166
167
0
                        const basegfx::B2DPoint aRelativePoint(aBackTransform * getDiscreteHitPosition());
168
0
                        const basegfx::B2DRange aUnitRange(0.0, 0.0, 1.0, 1.0);
169
170
0
                        if(aUnitRange.isInside(aRelativePoint))
171
0
                        {
172
                            // aRelativePoint.getX() == 0.0 -> 0
173
                            // aRelativePoint.getX() == 0.999... -> aSizePixel.Width() - 1
174
                            // Since isInside includes upper bound (1.0), force also this:
175
                            // aRelativePoint.getX() == 1.0 -> aSizePixel.Width() - 1
176
0
                            sal_Int32 nX(aRelativePoint.getX() * aSizePixel.Width());
177
0
                            if (nX == aSizePixel.Width())
178
0
                                --nX;
179
0
                            sal_Int32 nY(aRelativePoint.getY() * aSizePixel.Height());
180
0
                            if (nY == aSizePixel.Height())
181
0
                                --nY;
182
183
0
                            mbHit = (0 != rBitmap.GetPixelColor(nX, nY).GetAlpha());
184
0
                        }
185
0
                    }
186
0
                    else
187
0
                    {
188
                        // fallback to standard HitTest
189
0
                        const basegfx::B2DPolygon aOutline(basegfx::utils::createPolygonFromRect(aRange));
190
0
                        mbHit = checkFillHitWithTolerance(basegfx::B2DPolyPolygon(aOutline), getDiscreteHitTolerance());
191
0
                    }
192
0
                }
193
0
            }
194
0
        }
195
196
        void HitTestProcessor2D::check3DHit(const primitive2d::ScenePrimitive2D& rCandidate)
197
0
        {
198
            // calculate relative point in unified 2D scene
199
0
            const basegfx::B2DPoint aLogicHitPosition(getViewInformation2D().getInverseObjectToViewTransformation() * getDiscreteHitPosition());
200
201
            // use bitmap check in ScenePrimitive2D
202
0
            bool bTryFastResult(false);
203
204
0
            if(rCandidate.tryToCheckLastVisualisationDirectHit(aLogicHitPosition, bTryFastResult))
205
0
            {
206
0
                mbHit = bTryFastResult;
207
0
            }
208
0
            else
209
0
            {
210
0
                basegfx::B2DHomMatrix aInverseSceneTransform(rCandidate.getObjectTransformation());
211
0
                aInverseSceneTransform.invert();
212
0
                const basegfx::B2DPoint aRelativePoint(aInverseSceneTransform * aLogicHitPosition);
213
214
                // check if test point is inside scene's unified area at all
215
0
                if(aRelativePoint.getX() >= 0.0 && aRelativePoint.getX() <= 1.0
216
0
                    && aRelativePoint.getY() >= 0.0 && aRelativePoint.getY() <= 1.0)
217
0
                {
218
                    // get 3D view information
219
0
                    const geometry::ViewInformation3D& rObjectViewInformation3D = rCandidate.getViewInformation3D();
220
221
                    // create HitPoint Front and Back, transform to object coordinates
222
0
                    basegfx::B3DHomMatrix aViewToObject(rObjectViewInformation3D.getObjectToView());
223
0
                    aViewToObject.invert();
224
0
                    const basegfx::B3DPoint aFront(aViewToObject * basegfx::B3DPoint(aRelativePoint.getX(), aRelativePoint.getY(), 0.0));
225
0
                    const basegfx::B3DPoint aBack(aViewToObject * basegfx::B3DPoint(aRelativePoint.getX(), aRelativePoint.getY(), 1.0));
226
227
0
                    if(!aFront.equal(aBack))
228
0
                    {
229
0
                        const primitive3d::Primitive3DContainer& rPrimitives = rCandidate.getChildren3D();
230
231
0
                        if(!rPrimitives.empty())
232
0
                        {
233
                            // make BoundVolume empty and overlapping test for speedup
234
0
                            const basegfx::B3DRange aObjectRange(
235
0
                                    rPrimitives.getB3DRange(rObjectViewInformation3D));
236
237
0
                            if(!aObjectRange.isEmpty())
238
0
                            {
239
0
                                const basegfx::B3DRange aFrontBackRange(aFront, aBack);
240
241
0
                                if(aObjectRange.overlaps(aFrontBackRange))
242
0
                                {
243
                                    // bound volumes hit, geometric cut tests needed
244
0
                                    drawinglayer::processor3d::CutFindProcessor aCutFindProcessor(
245
0
                                        rObjectViewInformation3D,
246
0
                                        aFront,
247
0
                                        aBack,
248
0
                                        true);
249
0
                                    aCutFindProcessor.process(rPrimitives);
250
251
0
                                    mbHit = (!aCutFindProcessor.getCutPoints().empty());
252
0
                                }
253
0
                            }
254
0
                        }
255
0
                    }
256
0
                }
257
258
0
                if(!getHit())
259
0
                {
260
                    // empty 3D scene; Check for border hit
261
0
                    basegfx::B2DPolygon aOutline(basegfx::utils::createUnitPolygon());
262
0
                    aOutline.transform(rCandidate.getObjectTransformation());
263
264
0
                    mbHit = checkHairlineHitWithTolerance(aOutline, getDiscreteHitTolerance());
265
0
                }
266
0
            }
267
0
        }
268
269
        void HitTestProcessor2D::processBasePrimitive2D(const primitive2d::BasePrimitive2D& rCandidate)
270
0
        {
271
0
            if(getHit())
272
0
            {
273
                // stop processing as soon as a hit was recognized
274
0
                return;
275
0
            }
276
277
0
            switch(rCandidate.getPrimitive2DID())
278
0
            {
279
0
                case PRIMITIVE2D_ID_TRANSFORMPRIMITIVE2D :
280
0
                {
281
                    // remember current ViewInformation2D
282
0
                    const primitive2d::TransformPrimitive2D& rTransformCandidate(static_cast< const primitive2d::TransformPrimitive2D& >(rCandidate));
283
0
                    const geometry::ViewInformation2D aLastViewInformation2D(getViewInformation2D());
284
285
                    // create new local ViewInformation2D containing transformation
286
0
                    geometry::ViewInformation2D aViewInformation2D(getViewInformation2D());
287
0
                    aViewInformation2D.setObjectTransformation(getViewInformation2D().getObjectTransformation() * rTransformCandidate.getTransformation());
288
0
                    setViewInformation2D(aViewInformation2D);
289
290
                    // process child content recursively
291
0
                    process(rTransformCandidate.getChildren());
292
293
                    // restore transformations
294
0
                    setViewInformation2D(aLastViewInformation2D);
295
296
0
                    break;
297
0
                }
298
0
                case PRIMITIVE2D_ID_POLYGONHAIRLINEPRIMITIVE2D :
299
0
                {
300
0
                    if(!getHitTextOnly())
301
0
                    {
302
                        // create hairline in discrete coordinates
303
0
                        const primitive2d::PolygonHairlinePrimitive2D& rPolygonCandidate(static_cast< const primitive2d::PolygonHairlinePrimitive2D& >(rCandidate));
304
305
                        // use hairline test
306
0
                        mbHit = checkHairlineHitWithTolerance(rPolygonCandidate.getB2DPolygon(), getDiscreteHitTolerance());
307
0
                    }
308
309
0
                    break;
310
0
                }
311
0
                case PRIMITIVE2D_ID_POLYGONMARKERPRIMITIVE2D :
312
0
                {
313
0
                    if(!getHitTextOnly())
314
0
                    {
315
                        // handle marker like hairline; no need to decompose in dashes
316
0
                        const primitive2d::PolygonMarkerPrimitive2D& rPolygonCandidate(static_cast< const primitive2d::PolygonMarkerPrimitive2D& >(rCandidate));
317
318
                        // use hairline test
319
0
                        mbHit = checkHairlineHitWithTolerance(rPolygonCandidate.getB2DPolygon(), getDiscreteHitTolerance());
320
0
                    }
321
322
0
                    break;
323
0
                }
324
0
                case PRIMITIVE2D_ID_POLYGONSTROKEPRIMITIVE2D :
325
0
                {
326
0
                    if(!getHitTextOnly())
327
0
                    {
328
                        // handle stroke evtl. directly; no need to decompose to filled polygon outlines
329
0
                        const primitive2d::PolygonStrokePrimitive2D& rPolygonCandidate(static_cast< const primitive2d::PolygonStrokePrimitive2D& >(rCandidate));
330
0
                        const attribute::LineAttribute& rLineAttribute = rPolygonCandidate.getLineAttribute();
331
332
0
                        if(rLineAttribute.getWidth() > 0.0)
333
0
                        {
334
0
                            if(basegfx::B2DLineJoin::Miter == rLineAttribute.getLineJoin())
335
0
                            {
336
                                // if line is mitered, use decomposition since mitered line
337
                                // geometry may use more space than the geometry grown by half line width
338
0
                                process(rCandidate);
339
0
                            }
340
0
                            else
341
0
                            {
342
                                // for all other B2DLINEJOIN_* do a hairline HitTest with expanded tolerance
343
0
                                const basegfx::B2DVector aDiscreteHalfLineVector(getViewInformation2D().getObjectToViewTransformation()
344
0
                                    * basegfx::B2DVector(rLineAttribute.getWidth() * 0.5, rLineAttribute.getWidth() * 0.5));
345
0
                                mbHit = checkHairlineHitWithTolerance(
346
0
                                    rPolygonCandidate.getB2DPolygon(),
347
0
                                    getDiscreteHitTolerance() + aDiscreteHalfLineVector);
348
0
                            }
349
0
                        }
350
0
                        else
351
0
                        {
352
                            // hairline; fallback to hairline test. Do not decompose
353
                            // since this may decompose the hairline to dashes
354
0
                            mbHit = checkHairlineHitWithTolerance(rPolygonCandidate.getB2DPolygon(), getDiscreteHitTolerance());
355
0
                        }
356
0
                    }
357
358
0
                    break;
359
0
                }
360
0
                case PRIMITIVE2D_ID_POLYGONWAVEPRIMITIVE2D :
361
0
                {
362
0
                    if(!getHitTextOnly())
363
0
                    {
364
                        // do not use decompose; just handle like a line with width
365
0
                        const primitive2d::PolygonWavePrimitive2D& rPolygonCandidate(static_cast< const primitive2d::PolygonWavePrimitive2D& >(rCandidate));
366
0
                        double fLogicHitTolerance(0.0);
367
368
                        // if WaveHeight, grow by it
369
0
                        if(rPolygonCandidate.getWaveHeight() > 0.0)
370
0
                        {
371
0
                            fLogicHitTolerance += rPolygonCandidate.getWaveHeight();
372
0
                        }
373
374
                        // if line width, grow by it
375
0
                        if(rPolygonCandidate.getLineAttribute().getWidth() > 0.0)
376
0
                        {
377
0
                            fLogicHitTolerance += rPolygonCandidate.getLineAttribute().getWidth() * 0.5;
378
0
                        }
379
380
0
                        const basegfx::B2DVector aDiscreteHalfLineVector(getViewInformation2D().getObjectToViewTransformation()
381
0
                            * basegfx::B2DVector(fLogicHitTolerance, fLogicHitTolerance));
382
383
0
                        mbHit = checkHairlineHitWithTolerance(
384
0
                            rPolygonCandidate.getB2DPolygon(),
385
0
                            getDiscreteHitTolerance() + aDiscreteHalfLineVector);
386
0
                    }
387
388
0
                    break;
389
0
                }
390
0
                case PRIMITIVE2D_ID_POLYPOLYGONCOLORPRIMITIVE2D :
391
0
                {
392
0
                    if(!getHitTextOnly())
393
0
                    {
394
                        // create filled polyPolygon in discrete coordinates
395
0
                        const primitive2d::PolyPolygonColorPrimitive2D& rPolygonCandidate(static_cast< const primitive2d::PolyPolygonColorPrimitive2D& >(rCandidate));
396
397
                        // use fill hit test
398
0
                        mbHit = checkFillHitWithTolerance(rPolygonCandidate.getB2DPolyPolygon(), getDiscreteHitTolerance());
399
0
                    }
400
401
0
                    break;
402
0
                }
403
0
                case PRIMITIVE2D_ID_TRANSPARENCEPRIMITIVE2D :
404
0
                {
405
                    // sub-transparence group
406
0
                    const primitive2d::TransparencePrimitive2D& rTransCandidate(static_cast< const primitive2d::TransparencePrimitive2D& >(rCandidate));
407
408
                    // Currently the transparence content is not taken into account; only
409
                    // the children are recursively checked for hit. This may be refined for
410
                    // parts where the content is completely transparent if needed.
411
0
                    process(rTransCandidate.getChildren());
412
413
0
                    break;
414
0
                }
415
0
                case PRIMITIVE2D_ID_MASKPRIMITIVE2D :
416
0
                {
417
                    // create mask in discrete coordinates; only recursively continue
418
                    // with content when HitTest position is inside the mask
419
0
                    const primitive2d::MaskPrimitive2D& rMaskCandidate(static_cast< const primitive2d::MaskPrimitive2D& >(rCandidate));
420
421
                    // use fill hit test
422
0
                    if(checkFillHitWithTolerance(rMaskCandidate.getMask(), getDiscreteHitTolerance()))
423
0
                    {
424
                        // recursively HitTest children
425
0
                        process(rMaskCandidate.getChildren());
426
0
                    }
427
428
0
                    break;
429
0
                }
430
0
                case PRIMITIVE2D_ID_SCENEPRIMITIVE2D :
431
0
                {
432
0
                    if(!getHitTextOnly())
433
0
                    {
434
0
                        const primitive2d::ScenePrimitive2D& rScenePrimitive2D(
435
0
                            static_cast< const primitive2d::ScenePrimitive2D& >(rCandidate));
436
0
                        check3DHit(rScenePrimitive2D);
437
0
                    }
438
439
0
                    break;
440
0
                }
441
0
                case PRIMITIVE2D_ID_WRONGSPELLPRIMITIVE2D :
442
0
                case PRIMITIVE2D_ID_MARKERARRAYPRIMITIVE2D :
443
0
                case PRIMITIVE2D_ID_GRIDPRIMITIVE2D :
444
0
                case PRIMITIVE2D_ID_HELPLINEPRIMITIVE2D :
445
0
                {
446
                    // ignorable primitives
447
0
                    break;
448
0
                }
449
0
                case PRIMITIVE2D_ID_SHADOWPRIMITIVE2D :
450
0
                {
451
                    // Ignore shadows; we do not want to have shadows hittable.
452
                    // Remove this one to make shadows hittable on demand.
453
0
                    break;
454
0
                }
455
0
                case PRIMITIVE2D_ID_TEXTSIMPLEPORTIONPRIMITIVE2D :
456
0
                case PRIMITIVE2D_ID_TEXTDECORATEDPORTIONPRIMITIVE2D :
457
0
                {
458
                    // for text use the BoundRect of the primitive itself
459
0
                    const basegfx::B2DRange aRange(rCandidate.getB2DRange(getViewInformation2D()));
460
461
0
                    if(!aRange.isEmpty())
462
0
                    {
463
0
                        const basegfx::B2DPolygon aOutline(basegfx::utils::createPolygonFromRect(aRange));
464
0
                        mbHit = checkFillHitWithTolerance(basegfx::B2DPolyPolygon(aOutline), getDiscreteHitTolerance());
465
0
                    }
466
467
0
                    break;
468
0
                }
469
0
                case PRIMITIVE2D_ID_BITMAPALPHAPRIMITIVE2D :
470
0
                {
471
                    // avoid decompose of this primitive by handling directly
472
0
                    const primitive2d::BitmapAlphaPrimitive2D& rBitmapAlphaCandidate(static_cast< const primitive2d::BitmapAlphaPrimitive2D& >(rCandidate));
473
474
0
                    if (!basegfx::fTools::equal(rBitmapAlphaCandidate.getTransparency(), 1.0))
475
0
                    {
476
0
                        checkBitmapHit(
477
0
                            rCandidate.getB2DRange(getViewInformation2D()),
478
0
                            rBitmapAlphaCandidate.getBitmap(),
479
0
                            rBitmapAlphaCandidate.getTransform());
480
0
                    }
481
0
                    break;
482
0
                }
483
0
                case PRIMITIVE2D_ID_BITMAPPRIMITIVE2D :
484
0
                {
485
                    // use common tooling
486
0
                    const primitive2d::BitmapPrimitive2D& rBitmapCandidate(static_cast< const primitive2d::BitmapPrimitive2D& >(rCandidate));
487
0
                    checkBitmapHit(
488
0
                        rCandidate.getB2DRange(getViewInformation2D()),
489
0
                        rBitmapCandidate.getBitmap(),
490
0
                        rBitmapCandidate.getTransform());
491
0
                    break;
492
0
                }
493
0
                case PRIMITIVE2D_ID_METAFILEPRIMITIVE2D :
494
0
                case PRIMITIVE2D_ID_CONTROLPRIMITIVE2D :
495
0
                case PRIMITIVE2D_ID_FILLGRADIENTPRIMITIVE2D :
496
0
                case PRIMITIVE2D_ID_FILLGRAPHICPRIMITIVE2D :
497
0
                case PRIMITIVE2D_ID_FILLHATCHPRIMITIVE2D :
498
0
                case PRIMITIVE2D_ID_PAGEPREVIEWPRIMITIVE2D :
499
0
                case PRIMITIVE2D_ID_MEDIAPRIMITIVE2D:
500
0
                case PRIMITIVE2D_ID_ANIMATEDGRAPHICPRIMITIVE2D:
501
0
                case PRIMITIVE2D_ID_PATTERNFILLPRIMITIVE2D:
502
0
                {
503
0
                    if(!getHitTextOnly())
504
0
                    {
505
                        // Class of primitives for which just the BoundRect of the primitive itself
506
                        // will be used for HitTest currently.
507
                        //
508
                        // This may be refined in the future, e.g:
509
                        // - For Bitmaps, the mask and/or transparence information may be used
510
                        // - For MetaFiles, the MetaFile content may be used
511
0
                        const basegfx::B2DRange aRange(rCandidate.getB2DRange(getViewInformation2D()));
512
513
0
                        if(!aRange.isEmpty())
514
0
                        {
515
0
                            const basegfx::B2DPolygon aOutline(basegfx::utils::createPolygonFromRect(aRange));
516
0
                            mbHit = checkFillHitWithTolerance(basegfx::B2DPolyPolygon(aOutline), getDiscreteHitTolerance());
517
0
                        }
518
0
                    }
519
520
0
                    break;
521
0
                }
522
0
                case PRIMITIVE2D_ID_HIDDENGEOMETRYPRIMITIVE2D :
523
0
                {
524
                    // HiddenGeometryPrimitive2D; the default decomposition would return an empty sequence,
525
                    // so force this primitive to process its children directly if the switch is set
526
                    // (which is the default). Else, ignore invisible content
527
0
                    const primitive2d::HiddenGeometryPrimitive2D& rHiddenGeometry(static_cast< const primitive2d::HiddenGeometryPrimitive2D& >(rCandidate));
528
0
                    const primitive2d::Primitive2DContainer& rChildren = rHiddenGeometry.getChildren();
529
530
0
                    if(!rChildren.empty())
531
0
                    {
532
0
                        process(rChildren);
533
0
                    }
534
535
0
                    break;
536
0
                }
537
0
                case PRIMITIVE2D_ID_POINTARRAYPRIMITIVE2D :
538
0
                {
539
0
                    if(!getHitTextOnly())
540
0
                    {
541
0
                        const primitive2d::PointArrayPrimitive2D& rPointArrayCandidate(static_cast< const primitive2d::PointArrayPrimitive2D& >(rCandidate));
542
0
                        const std::vector< basegfx::B2DPoint >& rPositions = rPointArrayCandidate.getPositions();
543
0
                        const sal_uInt32 nCount(rPositions.size());
544
545
0
                        for(sal_uInt32 a(0); !getHit() && a < nCount; a++)
546
0
                        {
547
0
                            const basegfx::B2DPoint aPosition(getViewInformation2D().getObjectToViewTransformation() * rPositions[a]);
548
0
                            const basegfx::B2DVector aDistance(aPosition - getDiscreteHitPosition());
549
550
0
                            if (aDistance.getLength() <= std::max(getDiscreteHitTolerance().getX(),
551
0
                                                                  getDiscreteHitTolerance().getY()))
552
0
                            {
553
0
                                mbHit = true;
554
0
                            }
555
0
                        }
556
0
                    }
557
558
0
                    break;
559
0
                }
560
0
                default :
561
0
                {
562
                    // process recursively
563
0
                    process(rCandidate);
564
565
0
                    break;
566
0
                }
567
0
            }
568
569
0
            if (getHit() && getCollectHitStack())
570
0
            {
571
                /// push candidate to HitStack to create it. This only happens when a hit is found and
572
                /// creating the HitStack was requested (see collectHitStack)
573
0
                maHitStack.append(const_cast< primitive2d::BasePrimitive2D* >(&rCandidate));
574
0
            }
575
0
        }
576
577
} // end of namespace
578
579
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */