calc: on editing invalidation of view with different zoom is wrong
[LibreOffice.git] / drawinglayer / source / processor2d / hittestprocessor2d.cxx
blob9af3504a51130d3fa688ab5e38aee6adb469c617
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/PolygonHairlinePrimitive2D.hxx>
24 #include <drawinglayer/primitive2d/PolygonMarkerPrimitive2D.hxx>
25 #include <drawinglayer/primitive2d/PolygonWavePrimitive2D.hxx>
26 #include <drawinglayer/primitive2d/PolyPolygonColorPrimitive2D.hxx>
27 #include <basegfx/polygon/b2dpolygontools.hxx>
28 #include <basegfx/polygon/b2dpolypolygontools.hxx>
29 #include <drawinglayer/primitive2d/transparenceprimitive2d.hxx>
30 #include <drawinglayer/primitive2d/maskprimitive2d.hxx>
31 #include <drawinglayer/primitive2d/sceneprimitive2d.hxx>
32 #include <drawinglayer/primitive2d/pointarrayprimitive2d.hxx>
33 #include <basegfx/matrix/b3dhommatrix.hxx>
34 #include <drawinglayer/processor3d/cutfindprocessor3d.hxx>
35 #include <drawinglayer/primitive2d/hiddengeometryprimitive2d.hxx>
36 #include <drawinglayer/primitive2d/bitmapprimitive2d.hxx>
37 #include <comphelper/lok.hxx>
38 #include <toolkit/helper/vclunohelper.hxx>
40 namespace drawinglayer::processor2d
42 HitTestProcessor2D::HitTestProcessor2D(const geometry::ViewInformation2D& rViewInformation,
43 const basegfx::B2DPoint& rLogicHitPosition,
44 double fLogicHitTolerance,
45 bool bHitTextOnly)
46 : BaseProcessor2D(rViewInformation),
47 mfDiscreteHitTolerance(0.0),
48 mbCollectHitStack(false),
49 mbHit(false),
50 mbHitTextOnly(bHitTextOnly)
52 // init hit tolerance
53 mfDiscreteHitTolerance = fLogicHitTolerance;
55 if(basegfx::fTools::less(mfDiscreteHitTolerance, 0.0))
57 // ensure input parameter for hit tolerance is >= 0.0
58 mfDiscreteHitTolerance = 0.0;
60 else if(basegfx::fTools::more(mfDiscreteHitTolerance, 0.0))
62 // generate discrete hit tolerance
63 mfDiscreteHitTolerance = (getViewInformation2D().getObjectToViewTransformation()
64 * basegfx::B2DVector(mfDiscreteHitTolerance, 0.0)).getLength();
67 // generate discrete hit position
68 maDiscreteHitPosition = getViewInformation2D().getObjectToViewTransformation() * rLogicHitPosition;
71 HitTestProcessor2D::~HitTestProcessor2D()
75 bool HitTestProcessor2D::checkHairlineHitWithTolerance(
76 const basegfx::B2DPolygon& rPolygon,
77 double fDiscreteHitTolerance) const
79 basegfx::B2DPolygon aLocalPolygon(rPolygon);
80 aLocalPolygon.transform(getViewInformation2D().getObjectToViewTransformation());
82 // get discrete range
83 basegfx::B2DRange aPolygonRange(aLocalPolygon.getB2DRange());
85 if(basegfx::fTools::more(fDiscreteHitTolerance, 0.0))
87 aPolygonRange.grow(fDiscreteHitTolerance);
90 // do rough range test first
91 if(aPolygonRange.isInside(getDiscreteHitPosition()))
93 // check if a polygon edge is hit
94 return basegfx::utils::isInEpsilonRange(
95 aLocalPolygon,
96 getDiscreteHitPosition(),
97 fDiscreteHitTolerance);
100 return false;
103 bool HitTestProcessor2D::checkFillHitWithTolerance(
104 const basegfx::B2DPolyPolygon& rPolyPolygon,
105 double fDiscreteHitTolerance) const
107 bool bRetval(false);
108 basegfx::B2DPolyPolygon aLocalPolyPolygon(rPolyPolygon);
109 aLocalPolyPolygon.transform(getViewInformation2D().getObjectToViewTransformation());
111 // get discrete range
112 basegfx::B2DRange aPolygonRange(aLocalPolyPolygon.getB2DRange());
113 const bool bDiscreteHitToleranceUsed(basegfx::fTools::more(fDiscreteHitTolerance, 0.0));
115 if(bDiscreteHitToleranceUsed)
117 aPolygonRange.grow(fDiscreteHitTolerance);
120 // do rough range test first
121 if(aPolygonRange.isInside(getDiscreteHitPosition()))
123 // if a HitTolerance is given, check for polygon edge hit in epsilon first
124 if(bDiscreteHitToleranceUsed &&
125 basegfx::utils::isInEpsilonRange(
126 aLocalPolyPolygon,
127 getDiscreteHitPosition(),
128 fDiscreteHitTolerance))
130 bRetval = true;
133 // check for hit in filled polyPolygon
134 if(!bRetval && basegfx::utils::isInside(
135 aLocalPolyPolygon,
136 getDiscreteHitPosition(),
137 true))
139 bRetval = true;
143 return bRetval;
146 void HitTestProcessor2D::check3DHit(const primitive2d::ScenePrimitive2D& rCandidate)
148 // calculate relative point in unified 2D scene
149 const basegfx::B2DPoint aLogicHitPosition(getViewInformation2D().getInverseObjectToViewTransformation() * getDiscreteHitPosition());
151 // use bitmap check in ScenePrimitive2D
152 bool bTryFastResult(false);
154 if(rCandidate.tryToCheckLastVisualisationDirectHit(aLogicHitPosition, bTryFastResult))
156 mbHit = bTryFastResult;
158 else
160 basegfx::B2DHomMatrix aInverseSceneTransform(rCandidate.getObjectTransformation());
161 aInverseSceneTransform.invert();
162 const basegfx::B2DPoint aRelativePoint(aInverseSceneTransform * aLogicHitPosition);
164 // check if test point is inside scene's unified area at all
165 if(aRelativePoint.getX() >= 0.0 && aRelativePoint.getX() <= 1.0
166 && aRelativePoint.getY() >= 0.0 && aRelativePoint.getY() <= 1.0)
168 // get 3D view information
169 const geometry::ViewInformation3D& rObjectViewInformation3D = rCandidate.getViewInformation3D();
171 // create HitPoint Front and Back, transform to object coordinates
172 basegfx::B3DHomMatrix aViewToObject(rObjectViewInformation3D.getObjectToView());
173 aViewToObject.invert();
174 const basegfx::B3DPoint aFront(aViewToObject * basegfx::B3DPoint(aRelativePoint.getX(), aRelativePoint.getY(), 0.0));
175 const basegfx::B3DPoint aBack(aViewToObject * basegfx::B3DPoint(aRelativePoint.getX(), aRelativePoint.getY(), 1.0));
177 if(!aFront.equal(aBack))
179 const primitive3d::Primitive3DContainer& rPrimitives = rCandidate.getChildren3D();
181 if(!rPrimitives.empty())
183 // make BoundVolume empty and overlapping test for speedup
184 const basegfx::B3DRange aObjectRange(
185 rPrimitives.getB3DRange(rObjectViewInformation3D));
187 if(!aObjectRange.isEmpty())
189 const basegfx::B3DRange aFrontBackRange(aFront, aBack);
191 if(aObjectRange.overlaps(aFrontBackRange))
193 // bound volumes hit, geometric cut tests needed
194 drawinglayer::processor3d::CutFindProcessor aCutFindProcessor(
195 rObjectViewInformation3D,
196 aFront,
197 aBack,
198 true);
199 aCutFindProcessor.process(rPrimitives);
201 mbHit = (!aCutFindProcessor.getCutPoints().empty());
208 if(!getHit())
210 // empty 3D scene; Check for border hit
211 basegfx::B2DPolygon aOutline(basegfx::utils::createUnitPolygon());
212 aOutline.transform(rCandidate.getObjectTransformation());
214 mbHit = checkHairlineHitWithTolerance(aOutline, getDiscreteHitTolerance());
219 void HitTestProcessor2D::processBasePrimitive2D(const primitive2d::BasePrimitive2D& rCandidate)
221 if(getHit())
223 // stop processing as soon as a hit was recognized
224 return;
227 switch(rCandidate.getPrimitive2DID())
229 case PRIMITIVE2D_ID_TRANSFORMPRIMITIVE2D :
231 // remember current ViewInformation2D
232 const primitive2d::TransformPrimitive2D& rTransformCandidate(static_cast< const primitive2d::TransformPrimitive2D& >(rCandidate));
233 const geometry::ViewInformation2D aLastViewInformation2D(getViewInformation2D());
235 // create new local ViewInformation2D containing transformation
236 geometry::ViewInformation2D aViewInformation2D(getViewInformation2D());
237 aViewInformation2D.setObjectTransformation(getViewInformation2D().getObjectTransformation() * rTransformCandidate.getTransformation());
238 updateViewInformation(aViewInformation2D);
240 // process child content recursively
241 process(rTransformCandidate.getChildren());
243 // restore transformations
244 updateViewInformation(aLastViewInformation2D);
246 break;
248 case PRIMITIVE2D_ID_POLYGONHAIRLINEPRIMITIVE2D :
250 if(!getHitTextOnly())
252 // create hairline in discrete coordinates
253 const primitive2d::PolygonHairlinePrimitive2D& rPolygonCandidate(static_cast< const primitive2d::PolygonHairlinePrimitive2D& >(rCandidate));
255 // use hairline test
256 mbHit = checkHairlineHitWithTolerance(rPolygonCandidate.getB2DPolygon(), getDiscreteHitTolerance());
259 break;
261 case PRIMITIVE2D_ID_POLYGONMARKERPRIMITIVE2D :
263 if(!getHitTextOnly())
265 // handle marker like hairline; no need to decompose in dashes
266 const primitive2d::PolygonMarkerPrimitive2D& rPolygonCandidate(static_cast< const primitive2d::PolygonMarkerPrimitive2D& >(rCandidate));
268 // use hairline test
269 mbHit = checkHairlineHitWithTolerance(rPolygonCandidate.getB2DPolygon(), getDiscreteHitTolerance());
272 break;
274 case PRIMITIVE2D_ID_POLYGONSTROKEPRIMITIVE2D :
276 if(!getHitTextOnly())
278 // handle stroke evtl. directly; no need to decompose to filled polygon outlines
279 const primitive2d::PolygonStrokePrimitive2D& rPolygonCandidate(static_cast< const primitive2d::PolygonStrokePrimitive2D& >(rCandidate));
280 const attribute::LineAttribute& rLineAttribute = rPolygonCandidate.getLineAttribute();
282 if(basegfx::fTools::more(rLineAttribute.getWidth(), 0.0))
284 if(basegfx::B2DLineJoin::Miter == rLineAttribute.getLineJoin())
286 // if line is mitered, use decomposition since mitered line
287 // geometry may use more space than the geometry grown by half line width
288 process(rCandidate);
290 else
292 // for all other B2DLINEJOIN_* do a hairline HitTest with expanded tolerance
293 const basegfx::B2DVector aDiscreteHalfLineVector(getViewInformation2D().getObjectToViewTransformation()
294 * basegfx::B2DVector(rLineAttribute.getWidth() * 0.5, 0.0));
295 mbHit = checkHairlineHitWithTolerance(
296 rPolygonCandidate.getB2DPolygon(),
297 getDiscreteHitTolerance() + aDiscreteHalfLineVector.getLength());
300 else
302 // hairline; fallback to hairline test. Do not decompose
303 // since this may decompose the hairline to dashes
304 mbHit = checkHairlineHitWithTolerance(rPolygonCandidate.getB2DPolygon(), getDiscreteHitTolerance());
308 break;
310 case PRIMITIVE2D_ID_POLYGONWAVEPRIMITIVE2D :
312 if(!getHitTextOnly())
314 // do not use decompose; just handle like a line with width
315 const primitive2d::PolygonWavePrimitive2D& rPolygonCandidate(static_cast< const primitive2d::PolygonWavePrimitive2D& >(rCandidate));
316 double fLogicHitTolerance(0.0);
318 // if WaveHeight, grow by it
319 if(basegfx::fTools::more(rPolygonCandidate.getWaveHeight(), 0.0))
321 fLogicHitTolerance += rPolygonCandidate.getWaveHeight();
324 // if line width, grow by it
325 if(basegfx::fTools::more(rPolygonCandidate.getLineAttribute().getWidth(), 0.0))
327 fLogicHitTolerance += rPolygonCandidate.getLineAttribute().getWidth() * 0.5;
330 const basegfx::B2DVector aDiscreteHalfLineVector(getViewInformation2D().getObjectToViewTransformation()
331 * basegfx::B2DVector(fLogicHitTolerance, 0.0));
333 mbHit = checkHairlineHitWithTolerance(
334 rPolygonCandidate.getB2DPolygon(),
335 getDiscreteHitTolerance() + aDiscreteHalfLineVector.getLength());
338 break;
340 case PRIMITIVE2D_ID_POLYPOLYGONCOLORPRIMITIVE2D :
342 if(!getHitTextOnly())
344 // create filled polyPolygon in discrete coordinates
345 const primitive2d::PolyPolygonColorPrimitive2D& rPolygonCandidate(static_cast< const primitive2d::PolyPolygonColorPrimitive2D& >(rCandidate));
347 // use fill hit test
348 mbHit = checkFillHitWithTolerance(rPolygonCandidate.getB2DPolyPolygon(), getDiscreteHitTolerance());
351 break;
353 case PRIMITIVE2D_ID_TRANSPARENCEPRIMITIVE2D :
355 // sub-transparence group
356 const primitive2d::TransparencePrimitive2D& rTransCandidate(static_cast< const primitive2d::TransparencePrimitive2D& >(rCandidate));
358 // Currently the transparence content is not taken into account; only
359 // the children are recursively checked for hit. This may be refined for
360 // parts where the content is completely transparent if needed.
361 process(rTransCandidate.getChildren());
363 break;
365 case PRIMITIVE2D_ID_MASKPRIMITIVE2D :
367 // create mask in discrete coordinates; only recursively continue
368 // with content when HitTest position is inside the mask
369 const primitive2d::MaskPrimitive2D& rMaskCandidate(static_cast< const primitive2d::MaskPrimitive2D& >(rCandidate));
371 // use fill hit test
372 if(checkFillHitWithTolerance(rMaskCandidate.getMask(), getDiscreteHitTolerance()))
374 // recursively HitTest children
375 process(rMaskCandidate.getChildren());
378 break;
380 case PRIMITIVE2D_ID_SCENEPRIMITIVE2D :
382 if(!getHitTextOnly())
384 const primitive2d::ScenePrimitive2D& rScenePrimitive2D(
385 static_cast< const primitive2d::ScenePrimitive2D& >(rCandidate));
386 check3DHit(rScenePrimitive2D);
389 break;
391 case PRIMITIVE2D_ID_WRONGSPELLPRIMITIVE2D :
392 case PRIMITIVE2D_ID_MARKERARRAYPRIMITIVE2D :
393 case PRIMITIVE2D_ID_GRIDPRIMITIVE2D :
394 case PRIMITIVE2D_ID_HELPLINEPRIMITIVE2D :
396 // ignorable primitives
397 break;
399 case PRIMITIVE2D_ID_SHADOWPRIMITIVE2D :
401 // Ignore shadows; we do not want to have shadows hittable.
402 // Remove this one to make shadows hittable on demand.
403 break;
405 case PRIMITIVE2D_ID_TEXTSIMPLEPORTIONPRIMITIVE2D :
406 case PRIMITIVE2D_ID_TEXTDECORATEDPORTIONPRIMITIVE2D :
408 // for text use the BoundRect of the primitive itself
409 const basegfx::B2DRange aRange(rCandidate.getB2DRange(getViewInformation2D()));
411 if(!aRange.isEmpty())
413 const basegfx::B2DPolygon aOutline(basegfx::utils::createPolygonFromRect(aRange));
414 mbHit = checkFillHitWithTolerance(basegfx::B2DPolyPolygon(aOutline), getDiscreteHitTolerance());
417 break;
419 case PRIMITIVE2D_ID_BITMAPPRIMITIVE2D :
421 if(!getHitTextOnly())
423 // The recently added BitmapEx::GetTransparency() makes it easy to extend
424 // the BitmapPrimitive2D HitTest to take the contained BitmapEx and it's
425 // transparency into account
426 const basegfx::B2DRange aRange(rCandidate.getB2DRange(getViewInformation2D()));
428 if(!aRange.isEmpty())
430 const primitive2d::BitmapPrimitive2D& rBitmapCandidate(static_cast< const primitive2d::BitmapPrimitive2D& >(rCandidate));
431 const BitmapEx aBitmapEx(rBitmapCandidate.getBitmap());
432 const Size& rSizePixel(aBitmapEx.GetSizePixel());
434 // When tiled rendering, don't bother with the pixel size of the candidate.
435 if(rSizePixel.Width() && rSizePixel.Height() && !comphelper::LibreOfficeKit::isActive())
437 basegfx::B2DHomMatrix aBackTransform(
438 getViewInformation2D().getObjectToViewTransformation() *
439 rBitmapCandidate.getTransform());
440 aBackTransform.invert();
442 const basegfx::B2DPoint aRelativePoint(aBackTransform * getDiscreteHitPosition());
443 const basegfx::B2DRange aUnitRange(0.0, 0.0, 1.0, 1.0);
445 if(aUnitRange.isInside(aRelativePoint))
447 const sal_Int32 nX(basegfx::fround(aRelativePoint.getX() * rSizePixel.Width()));
448 const sal_Int32 nY(basegfx::fround(aRelativePoint.getY() * rSizePixel.Height()));
450 mbHit = (0 != aBitmapEx.GetAlpha(nX, nY));
453 else
455 // fallback to standard HitTest
456 const basegfx::B2DPolygon aOutline(basegfx::utils::createPolygonFromRect(aRange));
457 mbHit = checkFillHitWithTolerance(basegfx::B2DPolyPolygon(aOutline), getDiscreteHitTolerance());
462 break;
464 case PRIMITIVE2D_ID_METAFILEPRIMITIVE2D :
465 case PRIMITIVE2D_ID_CONTROLPRIMITIVE2D :
466 case PRIMITIVE2D_ID_FILLGRADIENTPRIMITIVE2D :
467 case PRIMITIVE2D_ID_FILLGRAPHICPRIMITIVE2D :
468 case PRIMITIVE2D_ID_FILLHATCHPRIMITIVE2D :
469 case PRIMITIVE2D_ID_PAGEPREVIEWPRIMITIVE2D :
470 case PRIMITIVE2D_ID_MEDIAPRIMITIVE2D:
472 if(!getHitTextOnly())
474 // Class of primitives for which just the BoundRect of the primitive itself
475 // will be used for HitTest currently.
477 // This may be refined in the future, e.g:
478 // - For Bitmaps, the mask and/or transparence information may be used
479 // - For MetaFiles, the MetaFile content may be used
480 const basegfx::B2DRange aRange(rCandidate.getB2DRange(getViewInformation2D()));
482 if(!aRange.isEmpty())
484 const basegfx::B2DPolygon aOutline(basegfx::utils::createPolygonFromRect(aRange));
485 mbHit = checkFillHitWithTolerance(basegfx::B2DPolyPolygon(aOutline), getDiscreteHitTolerance());
489 break;
491 case PRIMITIVE2D_ID_HIDDENGEOMETRYPRIMITIVE2D :
493 // HiddenGeometryPrimitive2D; the default decomposition would return an empty sequence,
494 // so force this primitive to process its children directly if the switch is set
495 // (which is the default). Else, ignore invisible content
496 const primitive2d::HiddenGeometryPrimitive2D& rHiddenGeometry(static_cast< const primitive2d::HiddenGeometryPrimitive2D& >(rCandidate));
497 const primitive2d::Primitive2DContainer& rChildren = rHiddenGeometry.getChildren();
499 if(!rChildren.empty())
501 process(rChildren);
504 break;
506 case PRIMITIVE2D_ID_POINTARRAYPRIMITIVE2D :
508 if(!getHitTextOnly())
510 const primitive2d::PointArrayPrimitive2D& rPointArrayCandidate(static_cast< const primitive2d::PointArrayPrimitive2D& >(rCandidate));
511 const std::vector< basegfx::B2DPoint >& rPositions = rPointArrayCandidate.getPositions();
512 const sal_uInt32 nCount(rPositions.size());
514 for(sal_uInt32 a(0); !getHit() && a < nCount; a++)
516 const basegfx::B2DPoint aPosition(getViewInformation2D().getObjectToViewTransformation() * rPositions[a]);
517 const basegfx::B2DVector aDistance(aPosition - getDiscreteHitPosition());
519 if(aDistance.getLength() <= getDiscreteHitTolerance())
521 mbHit = true;
526 break;
528 default :
530 // process recursively
531 process(rCandidate);
533 break;
537 if (getHit() && getCollectHitStack())
539 /// push candidate to HitStack to create it. This only happens when a hit is found and
540 /// creating the HitStack was requested (see collectHitStack)
541 maHitStack.append(primitive2d::Primitive2DReference(const_cast< primitive2d::BasePrimitive2D* >(&rCandidate)));
545 } // end of namespace
547 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */