fdo#74697 Add Bluez 5 support for impress remote.
[LibreOffice.git] / drawinglayer / source / processor2d / hittestprocessor2d.cxx
blob2e09ba1bb1dfd4f6b9b355334c175aaa41aa13e3
1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 /*
3 * This file is part of the LibreOffice project.
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/.
9 * This file incorporates work covered by the following license notice:
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 .
20 #include <drawinglayer/processor2d/hittestprocessor2d.hxx>
21 #include <drawinglayer/primitive2d/drawinglayer_primitivetypes2d.hxx>
22 #include <drawinglayer/primitive2d/transformprimitive2d.hxx>
23 #include <drawinglayer/primitive2d/polygonprimitive2d.hxx>
24 #include <drawinglayer/primitive2d/polypolygonprimitive2d.hxx>
25 #include <basegfx/polygon/b2dpolygontools.hxx>
26 #include <basegfx/polygon/b2dpolypolygontools.hxx>
27 #include <drawinglayer/primitive2d/transparenceprimitive2d.hxx>
28 #include <drawinglayer/primitive2d/maskprimitive2d.hxx>
29 #include <drawinglayer/primitive2d/sceneprimitive2d.hxx>
30 #include <drawinglayer/primitive2d/pointarrayprimitive2d.hxx>
31 #include <basegfx/matrix/b3dhommatrix.hxx>
32 #include <drawinglayer/processor3d/cutfindprocessor3d.hxx>
33 #include <drawinglayer/primitive2d/hiddengeometryprimitive2d.hxx>
34 #include <drawinglayer/primitive2d/bitmapprimitive2d.hxx>
36 //////////////////////////////////////////////////////////////////////////////
38 namespace drawinglayer
40 namespace processor2d
42 HitTestProcessor2D::HitTestProcessor2D(const geometry::ViewInformation2D& rViewInformation,
43 const basegfx::B2DPoint& rLogicHitPosition,
44 double fLogicHitTolerance,
45 bool bHitTextOnly)
46 : BaseProcessor2D(rViewInformation),
47 maDiscreteHitPosition(),
48 mfDiscreteHitTolerance(0.0),
49 mbHit(false),
50 mbHitToleranceUsed(false),
51 mbUseInvisiblePrimitiveContent(true),
52 mbHitTextOnly(bHitTextOnly)
54 // init hit tolerance
55 mfDiscreteHitTolerance = fLogicHitTolerance;
57 if(basegfx::fTools::less(mfDiscreteHitTolerance, 0.0))
59 // ensure input parameter for hit tolerance is >= 0.0
60 mfDiscreteHitTolerance = 0.0;
62 else if(basegfx::fTools::more(mfDiscreteHitTolerance, 0.0))
64 // generate discrete hit tolerance
65 mfDiscreteHitTolerance = (getViewInformation2D().getObjectToViewTransformation()
66 * basegfx::B2DVector(mfDiscreteHitTolerance, 0.0)).getLength();
69 // gererate discrete hit position
70 maDiscreteHitPosition = getViewInformation2D().getObjectToViewTransformation() * rLogicHitPosition;
72 // check if HitTolerance is used
73 mbHitToleranceUsed = basegfx::fTools::more(getDiscreteHitTolerance(), 0.0);
76 HitTestProcessor2D::~HitTestProcessor2D()
80 bool HitTestProcessor2D::checkHairlineHitWithTolerance(
81 const basegfx::B2DPolygon& rPolygon,
82 double fDiscreteHitTolerance)
84 basegfx::B2DPolygon aLocalPolygon(rPolygon);
85 aLocalPolygon.transform(getViewInformation2D().getObjectToViewTransformation());
87 // get discrete range
88 basegfx::B2DRange aPolygonRange(aLocalPolygon.getB2DRange());
90 if(basegfx::fTools::more(fDiscreteHitTolerance, 0.0))
92 aPolygonRange.grow(fDiscreteHitTolerance);
95 // do rough range test first
96 if(aPolygonRange.isInside(getDiscreteHitPosition()))
98 // check if a polygon edge is hit
99 return basegfx::tools::isInEpsilonRange(
100 aLocalPolygon,
101 getDiscreteHitPosition(),
102 fDiscreteHitTolerance);
105 return false;
108 bool HitTestProcessor2D::checkFillHitWithTolerance(
109 const basegfx::B2DPolyPolygon& rPolyPolygon,
110 double fDiscreteHitTolerance)
112 bool bRetval(false);
113 basegfx::B2DPolyPolygon aLocalPolyPolygon(rPolyPolygon);
114 aLocalPolyPolygon.transform(getViewInformation2D().getObjectToViewTransformation());
116 // get discrete range
117 basegfx::B2DRange aPolygonRange(aLocalPolyPolygon.getB2DRange());
118 const bool bDiscreteHitToleranceUsed(basegfx::fTools::more(fDiscreteHitTolerance, 0.0));
120 if(bDiscreteHitToleranceUsed)
122 aPolygonRange.grow(fDiscreteHitTolerance);
125 // do rough range test first
126 if(aPolygonRange.isInside(getDiscreteHitPosition()))
128 // if a HitTolerance is given, check for polygon edge hit in epsilon first
129 if(bDiscreteHitToleranceUsed &&
130 basegfx::tools::isInEpsilonRange(
131 aLocalPolyPolygon,
132 getDiscreteHitPosition(),
133 fDiscreteHitTolerance))
135 bRetval = true;
138 // check for hit in filled polyPolygon
139 if(!bRetval && basegfx::tools::isInside(
140 aLocalPolyPolygon,
141 getDiscreteHitPosition(),
142 true))
144 bRetval = true;
148 return bRetval;
151 void HitTestProcessor2D::check3DHit(const primitive2d::ScenePrimitive2D& rCandidate)
153 // calculate relative point in unified 2D scene
154 const basegfx::B2DPoint aLogicHitPosition(getViewInformation2D().getInverseObjectToViewTransformation() * getDiscreteHitPosition());
156 // use bitmap check in ScenePrimitive2D
157 bool bTryFastResult(false);
159 if(rCandidate.tryToCheckLastVisualisationDirectHit(aLogicHitPosition, bTryFastResult))
161 mbHit = bTryFastResult;
163 else
165 basegfx::B2DHomMatrix aInverseSceneTransform(rCandidate.getObjectTransformation());
166 aInverseSceneTransform.invert();
167 const basegfx::B2DPoint aRelativePoint(aInverseSceneTransform * aLogicHitPosition);
169 // check if test point is inside scene's unified area at all
170 if(aRelativePoint.getX() >= 0.0 && aRelativePoint.getX() <= 1.0
171 && aRelativePoint.getY() >= 0.0 && aRelativePoint.getY() <= 1.0)
173 // get 3D view information
174 const geometry::ViewInformation3D& rObjectViewInformation3D = rCandidate.getViewInformation3D();
176 // create HitPoint Front and Back, transform to object coordinates
177 basegfx::B3DHomMatrix aViewToObject(rObjectViewInformation3D.getObjectToView());
178 aViewToObject.invert();
179 const basegfx::B3DPoint aFront(aViewToObject * basegfx::B3DPoint(aRelativePoint.getX(), aRelativePoint.getY(), 0.0));
180 const basegfx::B3DPoint aBack(aViewToObject * basegfx::B3DPoint(aRelativePoint.getX(), aRelativePoint.getY(), 1.0));
182 if(!aFront.equal(aBack))
184 const primitive3d::Primitive3DSequence& rPrimitives = rCandidate.getChildren3D();
186 if(rPrimitives.hasElements())
188 // make BoundVolume empty and overlapping test for speedup
189 const basegfx::B3DRange aObjectRange(
190 drawinglayer::primitive3d::getB3DRangeFromPrimitive3DSequence(
191 rPrimitives, rObjectViewInformation3D));
193 if(!aObjectRange.isEmpty())
195 const basegfx::B3DRange aFrontBackRange(aFront, aBack);
197 if(aObjectRange.overlaps(aFrontBackRange))
199 // bound volumes hit, geometric cut tests needed
200 drawinglayer::processor3d::CutFindProcessor aCutFindProcessor(
201 rObjectViewInformation3D,
202 aFront,
203 aBack,
204 true);
205 aCutFindProcessor.process(rPrimitives);
207 mbHit = (0 != aCutFindProcessor.getCutPoints().size());
214 // This is needed to check hit with 3D shadows, too. HitTest is without shadow
215 // to keep compatible with previous versions. Keeping here as reference
217 // if(!getHit())
218 // {
219 // // if scene has shadow, check hit with shadow, too
220 // const primitive2d::Primitive2DSequence xExtracted2DSceneShadow(rCandidate.getShadow2D(getViewInformation2D()));
222 // if(xExtracted2DSceneShadow.hasElements())
223 // {
224 // // proccess extracted 2D content
225 // process(xExtracted2DSceneShadow);
226 // }
227 // }
229 if(!getHit())
231 // empty 3D scene; Check for border hit
232 basegfx::B2DPolygon aOutline(basegfx::tools::createUnitPolygon());
233 aOutline.transform(rCandidate.getObjectTransformation());
235 mbHit = checkHairlineHitWithTolerance(aOutline, getDiscreteHitTolerance());
238 // This is what the previous version did. Keeping it here for reference
240 // // 2D Scene primitive containing 3D stuff; extract 2D contour in world coordinates
241 // // This may be refined later to an own 3D HitTest renderer which processes the 3D
242 // // geometry directly
243 // const primitive2d::ScenePrimitive2D& rScenePrimitive2DCandidate(static_cast< const primitive2d::ScenePrimitive2D& >(rCandidate));
244 // const primitive2d::Primitive2DSequence xExtracted2DSceneGeometry(rScenePrimitive2DCandidate.getGeometry2D());
245 // const primitive2d::Primitive2DSequence xExtracted2DSceneShadow(rScenePrimitive2DCandidate.getShadow2D(getViewInformation2D()));
247 // if(xExtracted2DSceneGeometry.hasElements() || xExtracted2DSceneShadow.hasElements())
248 // {
249 // // proccess extracted 2D content
250 // process(xExtracted2DSceneGeometry);
251 // process(xExtracted2DSceneShadow);
252 // }
253 // else
254 // {
255 // // empty 3D scene; Check for border hit
256 // const basegfx::B2DRange aRange(rCandidate.getB2DRange(getViewInformation2D()));
257 // if(!aRange.isEmpty())
258 // {
259 // const basegfx::B2DPolygon aOutline(basegfx::tools::createPolygonFromRect(aRange));
260 // mbHit = checkHairlineHitWithTolerance(aOutline, getDiscreteHitTolerance());
261 // }
262 // }
266 void HitTestProcessor2D::processBasePrimitive2D(const primitive2d::BasePrimitive2D& rCandidate)
268 if(getHit())
270 // stop processing as soon as a hit was recognized
271 return;
274 switch(rCandidate.getPrimitive2DID())
276 case PRIMITIVE2D_ID_TRANSFORMPRIMITIVE2D :
278 // remember current ViewInformation2D
279 const primitive2d::TransformPrimitive2D& rTransformCandidate(static_cast< const primitive2d::TransformPrimitive2D& >(rCandidate));
280 const geometry::ViewInformation2D aLastViewInformation2D(getViewInformation2D());
282 // create new local ViewInformation2D containing transformation
283 const geometry::ViewInformation2D aViewInformation2D(
284 getViewInformation2D().getObjectTransformation() * rTransformCandidate.getTransformation(),
285 getViewInformation2D().getViewTransformation(),
286 getViewInformation2D().getViewport(),
287 getViewInformation2D().getVisualizedPage(),
288 getViewInformation2D().getViewTime(),
289 getViewInformation2D().getExtendedInformationSequence());
290 updateViewInformation(aViewInformation2D);
292 // proccess child content recursively
293 process(rTransformCandidate.getChildren());
295 // restore transformations
296 updateViewInformation(aLastViewInformation2D);
298 break;
300 case PRIMITIVE2D_ID_POLYGONHAIRLINEPRIMITIVE2D :
302 if(!getHitTextOnly())
304 // create hairline in discrete coordinates
305 const primitive2d::PolygonHairlinePrimitive2D& rPolygonCandidate(static_cast< const primitive2d::PolygonHairlinePrimitive2D& >(rCandidate));
307 // use hairline test
308 mbHit = checkHairlineHitWithTolerance(rPolygonCandidate.getB2DPolygon(), getDiscreteHitTolerance());
311 break;
313 case PRIMITIVE2D_ID_POLYGONMARKERPRIMITIVE2D :
315 if(!getHitTextOnly())
317 // handle marker like hairline; no need to decompose in dashes
318 const primitive2d::PolygonMarkerPrimitive2D& rPolygonCandidate(static_cast< const primitive2d::PolygonMarkerPrimitive2D& >(rCandidate));
320 // use hairline test
321 mbHit = checkHairlineHitWithTolerance(rPolygonCandidate.getB2DPolygon(), getDiscreteHitTolerance());
324 break;
326 case PRIMITIVE2D_ID_POLYGONSTROKEPRIMITIVE2D :
328 if(!getHitTextOnly())
330 // handle stroke evtl. directly; no need to decompose to filled polygon outlines
331 const primitive2d::PolygonStrokePrimitive2D& rPolygonCandidate(static_cast< const primitive2d::PolygonStrokePrimitive2D& >(rCandidate));
332 const attribute::LineAttribute& rLineAttribute = rPolygonCandidate.getLineAttribute();
334 if(basegfx::fTools::more(rLineAttribute.getWidth(), 0.0))
336 if(basegfx::B2DLINEJOIN_MITER == rLineAttribute.getLineJoin())
338 // if line is mitered, use decomposition since mitered line
339 // geometry may use more space than the geometry grown by half line width
340 process(rCandidate.get2DDecomposition(getViewInformation2D()));
342 else
344 // for all other B2DLINEJOIN_* do a hairline HitTest with expanded tolerance
345 const basegfx::B2DVector aDiscreteHalfLineVector(getViewInformation2D().getObjectToViewTransformation()
346 * basegfx::B2DVector(rLineAttribute.getWidth() * 0.5, 0.0));
347 mbHit = checkHairlineHitWithTolerance(
348 rPolygonCandidate.getB2DPolygon(),
349 getDiscreteHitTolerance() + aDiscreteHalfLineVector.getLength());
352 else
354 // hairline; fallback to hairline test. Do not decompose
355 // since this may decompose the hairline to dashes
356 mbHit = checkHairlineHitWithTolerance(rPolygonCandidate.getB2DPolygon(), getDiscreteHitTolerance());
360 break;
362 case PRIMITIVE2D_ID_POLYGONWAVEPRIMITIVE2D :
364 if(!getHitTextOnly())
366 // do not use decompose; just handle like a line with width
367 const primitive2d::PolygonWavePrimitive2D& rPolygonCandidate(static_cast< const primitive2d::PolygonWavePrimitive2D& >(rCandidate));
368 double fLogicHitTolerance(0.0);
370 // if WaveHeight, grow by it
371 if(basegfx::fTools::more(rPolygonCandidate.getWaveHeight(), 0.0))
373 fLogicHitTolerance += rPolygonCandidate.getWaveHeight();
376 // if line width, grow by it
377 if(basegfx::fTools::more(rPolygonCandidate.getLineAttribute().getWidth(), 0.0))
379 fLogicHitTolerance += rPolygonCandidate.getLineAttribute().getWidth() * 0.5;
382 const basegfx::B2DVector aDiscreteHalfLineVector(getViewInformation2D().getObjectToViewTransformation()
383 * basegfx::B2DVector(fLogicHitTolerance, 0.0));
385 mbHit = checkHairlineHitWithTolerance(
386 rPolygonCandidate.getB2DPolygon(),
387 getDiscreteHitTolerance() + aDiscreteHalfLineVector.getLength());
390 break;
392 case PRIMITIVE2D_ID_POLYPOLYGONCOLORPRIMITIVE2D :
394 if(!getHitTextOnly())
396 // create filled polyPolygon in discrete coordinates
397 const primitive2d::PolyPolygonColorPrimitive2D& rPolygonCandidate(static_cast< const primitive2d::PolyPolygonColorPrimitive2D& >(rCandidate));
399 // use fill hit test
400 mbHit = checkFillHitWithTolerance(rPolygonCandidate.getB2DPolyPolygon(), getDiscreteHitTolerance());
403 break;
405 case PRIMITIVE2D_ID_TRANSPARENCEPRIMITIVE2D :
407 // sub-transparence group
408 const primitive2d::TransparencePrimitive2D& rTransCandidate(static_cast< const primitive2d::TransparencePrimitive2D& >(rCandidate));
410 // Currently the transparence content is not taken into account; only
411 // the children are recursively checked for hit. This may be refined for
412 // parts where the content is completely transparent if needed.
413 process(rTransCandidate.getChildren());
415 break;
417 case PRIMITIVE2D_ID_MASKPRIMITIVE2D :
419 // create mask in discrete coordinates; only recursively continue
420 // with content when HitTest position is inside the mask
421 const primitive2d::MaskPrimitive2D& rMaskCandidate(static_cast< const primitive2d::MaskPrimitive2D& >(rCandidate));
423 // use fill hit test
424 if(checkFillHitWithTolerance(rMaskCandidate.getMask(), getDiscreteHitTolerance()))
426 // recursively HitTest children
427 process(rMaskCandidate.getChildren());
430 break;
432 case PRIMITIVE2D_ID_SCENEPRIMITIVE2D :
434 if(!getHitTextOnly())
436 const primitive2d::ScenePrimitive2D& rScenePrimitive2D(
437 static_cast< const primitive2d::ScenePrimitive2D& >(rCandidate));
438 check3DHit(rScenePrimitive2D);
441 break;
443 case PRIMITIVE2D_ID_WRONGSPELLPRIMITIVE2D :
444 case PRIMITIVE2D_ID_MARKERARRAYPRIMITIVE2D :
445 case PRIMITIVE2D_ID_GRIDPRIMITIVE2D :
446 case PRIMITIVE2D_ID_HELPLINEPRIMITIVE2D :
448 // ignorable primitives
449 break;
451 case PRIMITIVE2D_ID_SHADOWPRIMITIVE2D :
453 // Ignore shadows; we do not want to have shadows hittable.
454 // Remove this one to make shadows hittable on demand.
455 break;
457 case PRIMITIVE2D_ID_TEXTSIMPLEPORTIONPRIMITIVE2D :
458 case PRIMITIVE2D_ID_TEXTDECORATEDPORTIONPRIMITIVE2D :
460 // for text use the BoundRect of the primitive itself
461 const basegfx::B2DRange aRange(rCandidate.getB2DRange(getViewInformation2D()));
463 if(!aRange.isEmpty())
465 const basegfx::B2DPolygon aOutline(basegfx::tools::createPolygonFromRect(aRange));
466 mbHit = checkFillHitWithTolerance(basegfx::B2DPolyPolygon(aOutline), getDiscreteHitTolerance());
469 break;
471 case PRIMITIVE2D_ID_BITMAPPRIMITIVE2D :
473 if(!getHitTextOnly())
475 // The recently added BitmapEx::GetTransparency() makes it easy to extend
476 // the BitmapPrimitive2D HitTest to take the contained BotmapEx and it's
477 // transparency into account
478 const basegfx::B2DRange aRange(rCandidate.getB2DRange(getViewInformation2D()));
480 if(!aRange.isEmpty())
482 const primitive2d::BitmapPrimitive2D& rBitmapCandidate(static_cast< const primitive2d::BitmapPrimitive2D& >(rCandidate));
483 const BitmapEx& rBitmapEx = rBitmapCandidate.getBitmapEx();
484 const Size& rSizePixel(rBitmapEx.GetSizePixel());
486 if(rSizePixel.Width() && rSizePixel.Height())
488 basegfx::B2DHomMatrix aBackTransform(
489 getViewInformation2D().getObjectToViewTransformation() *
490 rBitmapCandidate.getTransform());
491 aBackTransform.invert();
493 const basegfx::B2DPoint aRelativePoint(aBackTransform * getDiscreteHitPosition());
494 const basegfx::B2DRange aUnitRange(0.0, 0.0, 1.0, 1.0);
496 if(aUnitRange.isInside(aRelativePoint))
498 const sal_Int32 nX(basegfx::fround(aRelativePoint.getX() * rSizePixel.Width()));
499 const sal_Int32 nY(basegfx::fround(aRelativePoint.getY() * rSizePixel.Height()));
501 mbHit = (0xff != rBitmapEx.GetTransparency(nX, nY));
504 else
506 // fallback to standard HitTest
507 const basegfx::B2DPolygon aOutline(basegfx::tools::createPolygonFromRect(aRange));
508 mbHit = checkFillHitWithTolerance(basegfx::B2DPolyPolygon(aOutline), getDiscreteHitTolerance());
513 break;
515 case PRIMITIVE2D_ID_METAFILEPRIMITIVE2D :
516 case PRIMITIVE2D_ID_CONTROLPRIMITIVE2D :
517 case PRIMITIVE2D_ID_FILLGRADIENTPRIMITIVE2D :
518 case PRIMITIVE2D_ID_FILLHATCHPRIMITIVE2D :
519 case PRIMITIVE2D_ID_PAGEPREVIEWPRIMITIVE2D :
520 case PRIMITIVE2D_ID_MEDIAPRIMITIVE2D:
522 if(!getHitTextOnly())
524 // Class of primitives for which just the BoundRect of the primitive itself
525 // will be used for HitTest currently.
527 // This may be refined in the future, e.g:
528 // - For Bitamps, the mask and/or transparence information may be used
529 // - For MetaFiles, the MetaFile content may be used
530 const basegfx::B2DRange aRange(rCandidate.getB2DRange(getViewInformation2D()));
532 if(!aRange.isEmpty())
534 const basegfx::B2DPolygon aOutline(basegfx::tools::createPolygonFromRect(aRange));
535 mbHit = checkFillHitWithTolerance(basegfx::B2DPolyPolygon(aOutline), getDiscreteHitTolerance());
539 break;
541 case PRIMITIVE2D_ID_HIDDENGEOMETRYPRIMITIVE2D :
543 // HiddenGeometryPrimitive2D; the default decomposition would return an empty seqence,
544 // so force this primitive to process it's children directly if the switch is set
545 // (which is the default). Else, ignore invisible content
546 const primitive2d::HiddenGeometryPrimitive2D& rHiddenGeometry(static_cast< const primitive2d::HiddenGeometryPrimitive2D& >(rCandidate));
547 const primitive2d::Primitive2DSequence& rChildren = rHiddenGeometry.getChildren();
549 if(rChildren.hasElements())
551 if(getUseInvisiblePrimitiveContent())
553 process(rChildren);
557 break;
559 case PRIMITIVE2D_ID_POINTARRAYPRIMITIVE2D :
561 if(!getHitTextOnly())
563 const primitive2d::PointArrayPrimitive2D& rPointArrayCandidate(static_cast< const primitive2d::PointArrayPrimitive2D& >(rCandidate));
564 const std::vector< basegfx::B2DPoint >& rPositions = rPointArrayCandidate.getPositions();
565 const sal_uInt32 nCount(rPositions.size());
567 for(sal_uInt32 a(0); !getHit() && a < nCount; a++)
569 const basegfx::B2DPoint aPosition(getViewInformation2D().getObjectToViewTransformation() * rPositions[a]);
570 const basegfx::B2DVector aDistance(aPosition - getDiscreteHitPosition());
572 if(aDistance.getLength() <= getDiscreteHitTolerance())
574 mbHit = true;
579 break;
581 default :
583 // process recursively
584 process(rCandidate.get2DDecomposition(getViewInformation2D()));
586 break;
591 } // end of namespace processor2d
592 } // end of namespace drawinglayer
594 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */