Version 5.2.6.1, tag libreoffice-5.2.6.1
[LibreOffice.git] / drawinglayer / source / processor2d / hittestprocessor2d.cxx
blob4853a2838affa8354c558b5c43c532dd720d7c4e
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>
35 #include <comphelper/lok.hxx>
37 namespace drawinglayer
39 namespace processor2d
41 HitTestProcessor2D::HitTestProcessor2D(const geometry::ViewInformation2D& rViewInformation,
42 const basegfx::B2DPoint& rLogicHitPosition,
43 double fLogicHitTolerance,
44 bool bHitTextOnly)
45 : BaseProcessor2D(rViewInformation),
46 maDiscreteHitPosition(),
47 mfDiscreteHitTolerance(0.0),
48 mbHit(false),
49 mbHitToleranceUsed(false),
50 mbUseInvisiblePrimitiveContent(true),
51 mbHitTextOnly(bHitTextOnly)
53 // init hit tolerance
54 mfDiscreteHitTolerance = fLogicHitTolerance;
56 if(basegfx::fTools::less(mfDiscreteHitTolerance, 0.0))
58 // ensure input parameter for hit tolerance is >= 0.0
59 mfDiscreteHitTolerance = 0.0;
61 else if(basegfx::fTools::more(mfDiscreteHitTolerance, 0.0))
63 // generate discrete hit tolerance
64 mfDiscreteHitTolerance = (getViewInformation2D().getObjectToViewTransformation()
65 * basegfx::B2DVector(mfDiscreteHitTolerance, 0.0)).getLength();
68 // gererate discrete hit position
69 maDiscreteHitPosition = getViewInformation2D().getObjectToViewTransformation() * rLogicHitPosition;
71 // check if HitTolerance is used
72 mbHitToleranceUsed = basegfx::fTools::more(getDiscreteHitTolerance(), 0.0);
75 HitTestProcessor2D::~HitTestProcessor2D()
79 bool HitTestProcessor2D::checkHairlineHitWithTolerance(
80 const basegfx::B2DPolygon& rPolygon,
81 double fDiscreteHitTolerance)
83 basegfx::B2DPolygon aLocalPolygon(rPolygon);
84 aLocalPolygon.transform(getViewInformation2D().getObjectToViewTransformation());
86 // get discrete range
87 basegfx::B2DRange aPolygonRange(aLocalPolygon.getB2DRange());
89 if(basegfx::fTools::more(fDiscreteHitTolerance, 0.0))
91 aPolygonRange.grow(fDiscreteHitTolerance);
94 // do rough range test first
95 if(aPolygonRange.isInside(getDiscreteHitPosition()))
97 // check if a polygon edge is hit
98 return basegfx::tools::isInEpsilonRange(
99 aLocalPolygon,
100 getDiscreteHitPosition(),
101 fDiscreteHitTolerance);
104 return false;
107 bool HitTestProcessor2D::checkFillHitWithTolerance(
108 const basegfx::B2DPolyPolygon& rPolyPolygon,
109 double fDiscreteHitTolerance)
111 bool bRetval(false);
112 basegfx::B2DPolyPolygon aLocalPolyPolygon(rPolyPolygon);
113 aLocalPolyPolygon.transform(getViewInformation2D().getObjectToViewTransformation());
115 // get discrete range
116 basegfx::B2DRange aPolygonRange(aLocalPolyPolygon.getB2DRange());
117 const bool bDiscreteHitToleranceUsed(basegfx::fTools::more(fDiscreteHitTolerance, 0.0));
119 if(bDiscreteHitToleranceUsed)
121 aPolygonRange.grow(fDiscreteHitTolerance);
124 // do rough range test first
125 if(aPolygonRange.isInside(getDiscreteHitPosition()))
127 // if a HitTolerance is given, check for polygon edge hit in epsilon first
128 if(bDiscreteHitToleranceUsed &&
129 basegfx::tools::isInEpsilonRange(
130 aLocalPolyPolygon,
131 getDiscreteHitPosition(),
132 fDiscreteHitTolerance))
134 bRetval = true;
137 // check for hit in filled polyPolygon
138 if(!bRetval && basegfx::tools::isInside(
139 aLocalPolyPolygon,
140 getDiscreteHitPosition(),
141 true))
143 bRetval = true;
147 return bRetval;
150 void HitTestProcessor2D::check3DHit(const primitive2d::ScenePrimitive2D& rCandidate)
152 // calculate relative point in unified 2D scene
153 const basegfx::B2DPoint aLogicHitPosition(getViewInformation2D().getInverseObjectToViewTransformation() * getDiscreteHitPosition());
155 // use bitmap check in ScenePrimitive2D
156 bool bTryFastResult(false);
158 if(rCandidate.tryToCheckLastVisualisationDirectHit(aLogicHitPosition, bTryFastResult))
160 mbHit = bTryFastResult;
162 else
164 basegfx::B2DHomMatrix aInverseSceneTransform(rCandidate.getObjectTransformation());
165 aInverseSceneTransform.invert();
166 const basegfx::B2DPoint aRelativePoint(aInverseSceneTransform * aLogicHitPosition);
168 // check if test point is inside scene's unified area at all
169 if(aRelativePoint.getX() >= 0.0 && aRelativePoint.getX() <= 1.0
170 && aRelativePoint.getY() >= 0.0 && aRelativePoint.getY() <= 1.0)
172 // get 3D view information
173 const geometry::ViewInformation3D& rObjectViewInformation3D = rCandidate.getViewInformation3D();
175 // create HitPoint Front and Back, transform to object coordinates
176 basegfx::B3DHomMatrix aViewToObject(rObjectViewInformation3D.getObjectToView());
177 aViewToObject.invert();
178 const basegfx::B3DPoint aFront(aViewToObject * basegfx::B3DPoint(aRelativePoint.getX(), aRelativePoint.getY(), 0.0));
179 const basegfx::B3DPoint aBack(aViewToObject * basegfx::B3DPoint(aRelativePoint.getX(), aRelativePoint.getY(), 1.0));
181 if(!aFront.equal(aBack))
183 const primitive3d::Primitive3DContainer& rPrimitives = rCandidate.getChildren3D();
185 if(!rPrimitives.empty())
187 // make BoundVolume empty and overlapping test for speedup
188 const basegfx::B3DRange aObjectRange(
189 rPrimitives.getB3DRange(rObjectViewInformation3D));
191 if(!aObjectRange.isEmpty())
193 const basegfx::B3DRange aFrontBackRange(aFront, aBack);
195 if(aObjectRange.overlaps(aFrontBackRange))
197 // bound volumes hit, geometric cut tests needed
198 drawinglayer::processor3d::CutFindProcessor aCutFindProcessor(
199 rObjectViewInformation3D,
200 aFront,
201 aBack,
202 true);
203 aCutFindProcessor.process(rPrimitives);
205 mbHit = (0 != aCutFindProcessor.getCutPoints().size());
212 if(!getHit())
214 // empty 3D scene; Check for border hit
215 basegfx::B2DPolygon aOutline(basegfx::tools::createUnitPolygon());
216 aOutline.transform(rCandidate.getObjectTransformation());
218 mbHit = checkHairlineHitWithTolerance(aOutline, getDiscreteHitTolerance());
223 void HitTestProcessor2D::processBasePrimitive2D(const primitive2d::BasePrimitive2D& rCandidate)
225 if(getHit())
227 // stop processing as soon as a hit was recognized
228 return;
231 switch(rCandidate.getPrimitive2DID())
233 case PRIMITIVE2D_ID_TRANSFORMPRIMITIVE2D :
235 // remember current ViewInformation2D
236 const primitive2d::TransformPrimitive2D& rTransformCandidate(static_cast< const primitive2d::TransformPrimitive2D& >(rCandidate));
237 const geometry::ViewInformation2D aLastViewInformation2D(getViewInformation2D());
239 // create new local ViewInformation2D containing transformation
240 const geometry::ViewInformation2D aViewInformation2D(
241 getViewInformation2D().getObjectTransformation() * rTransformCandidate.getTransformation(),
242 getViewInformation2D().getViewTransformation(),
243 getViewInformation2D().getViewport(),
244 getViewInformation2D().getVisualizedPage(),
245 getViewInformation2D().getViewTime(),
246 getViewInformation2D().getExtendedInformationSequence());
247 updateViewInformation(aViewInformation2D);
249 // process child content recursively
250 process(rTransformCandidate.getChildren());
252 // restore transformations
253 updateViewInformation(aLastViewInformation2D);
255 break;
257 case PRIMITIVE2D_ID_POLYGONHAIRLINEPRIMITIVE2D :
259 if(!getHitTextOnly())
261 // create hairline in discrete coordinates
262 const primitive2d::PolygonHairlinePrimitive2D& rPolygonCandidate(static_cast< const primitive2d::PolygonHairlinePrimitive2D& >(rCandidate));
264 // use hairline test
265 mbHit = checkHairlineHitWithTolerance(rPolygonCandidate.getB2DPolygon(), getDiscreteHitTolerance());
268 break;
270 case PRIMITIVE2D_ID_POLYGONMARKERPRIMITIVE2D :
272 if(!getHitTextOnly())
274 // handle marker like hairline; no need to decompose in dashes
275 const primitive2d::PolygonMarkerPrimitive2D& rPolygonCandidate(static_cast< const primitive2d::PolygonMarkerPrimitive2D& >(rCandidate));
277 // use hairline test
278 mbHit = checkHairlineHitWithTolerance(rPolygonCandidate.getB2DPolygon(), getDiscreteHitTolerance());
281 break;
283 case PRIMITIVE2D_ID_POLYGONSTROKEPRIMITIVE2D :
285 if(!getHitTextOnly())
287 // handle stroke evtl. directly; no need to decompose to filled polygon outlines
288 const primitive2d::PolygonStrokePrimitive2D& rPolygonCandidate(static_cast< const primitive2d::PolygonStrokePrimitive2D& >(rCandidate));
289 const attribute::LineAttribute& rLineAttribute = rPolygonCandidate.getLineAttribute();
291 if(basegfx::fTools::more(rLineAttribute.getWidth(), 0.0))
293 if(basegfx::B2DLineJoin::Miter == rLineAttribute.getLineJoin())
295 // if line is mitered, use decomposition since mitered line
296 // geometry may use more space than the geometry grown by half line width
297 process(rCandidate.get2DDecomposition(getViewInformation2D()));
299 else
301 // for all other B2DLINEJOIN_* do a hairline HitTest with expanded tolerance
302 const basegfx::B2DVector aDiscreteHalfLineVector(getViewInformation2D().getObjectToViewTransformation()
303 * basegfx::B2DVector(rLineAttribute.getWidth() * 0.5, 0.0));
304 mbHit = checkHairlineHitWithTolerance(
305 rPolygonCandidate.getB2DPolygon(),
306 getDiscreteHitTolerance() + aDiscreteHalfLineVector.getLength());
309 else
311 // hairline; fallback to hairline test. Do not decompose
312 // since this may decompose the hairline to dashes
313 mbHit = checkHairlineHitWithTolerance(rPolygonCandidate.getB2DPolygon(), getDiscreteHitTolerance());
317 break;
319 case PRIMITIVE2D_ID_POLYGONWAVEPRIMITIVE2D :
321 if(!getHitTextOnly())
323 // do not use decompose; just handle like a line with width
324 const primitive2d::PolygonWavePrimitive2D& rPolygonCandidate(static_cast< const primitive2d::PolygonWavePrimitive2D& >(rCandidate));
325 double fLogicHitTolerance(0.0);
327 // if WaveHeight, grow by it
328 if(basegfx::fTools::more(rPolygonCandidate.getWaveHeight(), 0.0))
330 fLogicHitTolerance += rPolygonCandidate.getWaveHeight();
333 // if line width, grow by it
334 if(basegfx::fTools::more(rPolygonCandidate.getLineAttribute().getWidth(), 0.0))
336 fLogicHitTolerance += rPolygonCandidate.getLineAttribute().getWidth() * 0.5;
339 const basegfx::B2DVector aDiscreteHalfLineVector(getViewInformation2D().getObjectToViewTransformation()
340 * basegfx::B2DVector(fLogicHitTolerance, 0.0));
342 mbHit = checkHairlineHitWithTolerance(
343 rPolygonCandidate.getB2DPolygon(),
344 getDiscreteHitTolerance() + aDiscreteHalfLineVector.getLength());
347 break;
349 case PRIMITIVE2D_ID_POLYPOLYGONCOLORPRIMITIVE2D :
351 if(!getHitTextOnly())
353 // create filled polyPolygon in discrete coordinates
354 const primitive2d::PolyPolygonColorPrimitive2D& rPolygonCandidate(static_cast< const primitive2d::PolyPolygonColorPrimitive2D& >(rCandidate));
356 // use fill hit test
357 mbHit = checkFillHitWithTolerance(rPolygonCandidate.getB2DPolyPolygon(), getDiscreteHitTolerance());
360 break;
362 case PRIMITIVE2D_ID_TRANSPARENCEPRIMITIVE2D :
364 // sub-transparence group
365 const primitive2d::TransparencePrimitive2D& rTransCandidate(static_cast< const primitive2d::TransparencePrimitive2D& >(rCandidate));
367 // Currently the transparence content is not taken into account; only
368 // the children are recursively checked for hit. This may be refined for
369 // parts where the content is completely transparent if needed.
370 process(rTransCandidate.getChildren());
372 break;
374 case PRIMITIVE2D_ID_MASKPRIMITIVE2D :
376 // create mask in discrete coordinates; only recursively continue
377 // with content when HitTest position is inside the mask
378 const primitive2d::MaskPrimitive2D& rMaskCandidate(static_cast< const primitive2d::MaskPrimitive2D& >(rCandidate));
380 // use fill hit test
381 if(checkFillHitWithTolerance(rMaskCandidate.getMask(), getDiscreteHitTolerance()))
383 // recursively HitTest children
384 process(rMaskCandidate.getChildren());
387 break;
389 case PRIMITIVE2D_ID_SCENEPRIMITIVE2D :
391 if(!getHitTextOnly())
393 const primitive2d::ScenePrimitive2D& rScenePrimitive2D(
394 static_cast< const primitive2d::ScenePrimitive2D& >(rCandidate));
395 check3DHit(rScenePrimitive2D);
398 break;
400 case PRIMITIVE2D_ID_WRONGSPELLPRIMITIVE2D :
401 case PRIMITIVE2D_ID_MARKERARRAYPRIMITIVE2D :
402 case PRIMITIVE2D_ID_GRIDPRIMITIVE2D :
403 case PRIMITIVE2D_ID_HELPLINEPRIMITIVE2D :
405 // ignorable primitives
406 break;
408 case PRIMITIVE2D_ID_SHADOWPRIMITIVE2D :
410 // Ignore shadows; we do not want to have shadows hittable.
411 // Remove this one to make shadows hittable on demand.
412 break;
414 case PRIMITIVE2D_ID_TEXTSIMPLEPORTIONPRIMITIVE2D :
415 case PRIMITIVE2D_ID_TEXTDECORATEDPORTIONPRIMITIVE2D :
417 // for text use the BoundRect of the primitive itself
418 const basegfx::B2DRange aRange(rCandidate.getB2DRange(getViewInformation2D()));
420 if(!aRange.isEmpty())
422 const basegfx::B2DPolygon aOutline(basegfx::tools::createPolygonFromRect(aRange));
423 mbHit = checkFillHitWithTolerance(basegfx::B2DPolyPolygon(aOutline), getDiscreteHitTolerance());
426 break;
428 case PRIMITIVE2D_ID_BITMAPPRIMITIVE2D :
430 if(!getHitTextOnly())
432 // The recently added BitmapEx::GetTransparency() makes it easy to extend
433 // the BitmapPrimitive2D HitTest to take the contained BotmapEx and it's
434 // transparency into account
435 const basegfx::B2DRange aRange(rCandidate.getB2DRange(getViewInformation2D()));
437 if(!aRange.isEmpty())
439 const primitive2d::BitmapPrimitive2D& rBitmapCandidate(static_cast< const primitive2d::BitmapPrimitive2D& >(rCandidate));
440 const BitmapEx& rBitmapEx = rBitmapCandidate.getBitmapEx();
441 const Size& rSizePixel(rBitmapEx.GetSizePixel());
443 // When tiled rendering, don't bother with the pixel size of the candidate.
444 if(rSizePixel.Width() && rSizePixel.Height() && !comphelper::LibreOfficeKit::isActive())
446 basegfx::B2DHomMatrix aBackTransform(
447 getViewInformation2D().getObjectToViewTransformation() *
448 rBitmapCandidate.getTransform());
449 aBackTransform.invert();
451 const basegfx::B2DPoint aRelativePoint(aBackTransform * getDiscreteHitPosition());
452 const basegfx::B2DRange aUnitRange(0.0, 0.0, 1.0, 1.0);
454 if(aUnitRange.isInside(aRelativePoint))
456 const sal_Int32 nX(basegfx::fround(aRelativePoint.getX() * rSizePixel.Width()));
457 const sal_Int32 nY(basegfx::fround(aRelativePoint.getY() * rSizePixel.Height()));
459 mbHit = (0xff != rBitmapEx.GetTransparency(nX, nY));
462 else
464 // fallback to standard HitTest
465 const basegfx::B2DPolygon aOutline(basegfx::tools::createPolygonFromRect(aRange));
466 mbHit = checkFillHitWithTolerance(basegfx::B2DPolyPolygon(aOutline), getDiscreteHitTolerance());
471 break;
473 case PRIMITIVE2D_ID_METAFILEPRIMITIVE2D :
474 case PRIMITIVE2D_ID_CONTROLPRIMITIVE2D :
475 case PRIMITIVE2D_ID_FILLGRADIENTPRIMITIVE2D :
476 case PRIMITIVE2D_ID_FILLHATCHPRIMITIVE2D :
477 case PRIMITIVE2D_ID_PAGEPREVIEWPRIMITIVE2D :
478 case PRIMITIVE2D_ID_MEDIAPRIMITIVE2D:
480 if(!getHitTextOnly())
482 // Class of primitives for which just the BoundRect of the primitive itself
483 // will be used for HitTest currently.
485 // This may be refined in the future, e.g:
486 // - For Bitmaps, the mask and/or transparence information may be used
487 // - For MetaFiles, the MetaFile content may be used
488 const basegfx::B2DRange aRange(rCandidate.getB2DRange(getViewInformation2D()));
490 if(!aRange.isEmpty())
492 const basegfx::B2DPolygon aOutline(basegfx::tools::createPolygonFromRect(aRange));
493 mbHit = checkFillHitWithTolerance(basegfx::B2DPolyPolygon(aOutline), getDiscreteHitTolerance());
497 break;
499 case PRIMITIVE2D_ID_HIDDENGEOMETRYPRIMITIVE2D :
501 // HiddenGeometryPrimitive2D; the default decomposition would return an empty seqence,
502 // so force this primitive to process its children directly if the switch is set
503 // (which is the default). Else, ignore invisible content
504 const primitive2d::HiddenGeometryPrimitive2D& rHiddenGeometry(static_cast< const primitive2d::HiddenGeometryPrimitive2D& >(rCandidate));
505 const primitive2d::Primitive2DContainer& rChildren = rHiddenGeometry.getChildren();
507 if(!rChildren.empty())
509 if(getUseInvisiblePrimitiveContent())
511 process(rChildren);
515 break;
517 case PRIMITIVE2D_ID_POINTARRAYPRIMITIVE2D :
519 if(!getHitTextOnly())
521 const primitive2d::PointArrayPrimitive2D& rPointArrayCandidate(static_cast< const primitive2d::PointArrayPrimitive2D& >(rCandidate));
522 const std::vector< basegfx::B2DPoint >& rPositions = rPointArrayCandidate.getPositions();
523 const sal_uInt32 nCount(rPositions.size());
525 for(sal_uInt32 a(0); !getHit() && a < nCount; a++)
527 const basegfx::B2DPoint aPosition(getViewInformation2D().getObjectToViewTransformation() * rPositions[a]);
528 const basegfx::B2DVector aDistance(aPosition - getDiscreteHitPosition());
530 if(aDistance.getLength() <= getDiscreteHitTolerance())
532 mbHit = true;
537 break;
539 default :
541 // process recursively
542 process(rCandidate.get2DDecomposition(getViewInformation2D()));
544 break;
549 } // end of namespace processor2d
550 } // end of namespace drawinglayer
552 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */