update emoji autocorrect entries from po-files
[LibreOffice.git] / drawinglayer / source / processor2d / hittestprocessor2d.cxx
blob4c5602d118d53195514499f6f8524b1eb7ff3a64
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>
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 bool bTiledRendering)
47 : BaseProcessor2D(rViewInformation),
48 maDiscreteHitPosition(),
49 mfDiscreteHitTolerance(0.0),
50 mbHit(false),
51 mbHitToleranceUsed(false),
52 mbUseInvisiblePrimitiveContent(true),
53 mbHitTextOnly(bHitTextOnly),
54 mbTiledRendering(bTiledRendering)
56 // init hit tolerance
57 mfDiscreteHitTolerance = fLogicHitTolerance;
59 if(basegfx::fTools::less(mfDiscreteHitTolerance, 0.0))
61 // ensure input parameter for hit tolerance is >= 0.0
62 mfDiscreteHitTolerance = 0.0;
64 else if(basegfx::fTools::more(mfDiscreteHitTolerance, 0.0))
66 // generate discrete hit tolerance
67 mfDiscreteHitTolerance = (getViewInformation2D().getObjectToViewTransformation()
68 * basegfx::B2DVector(mfDiscreteHitTolerance, 0.0)).getLength();
71 // gererate discrete hit position
72 maDiscreteHitPosition = getViewInformation2D().getObjectToViewTransformation() * rLogicHitPosition;
74 // check if HitTolerance is used
75 mbHitToleranceUsed = basegfx::fTools::more(getDiscreteHitTolerance(), 0.0);
78 HitTestProcessor2D::~HitTestProcessor2D()
82 bool HitTestProcessor2D::checkHairlineHitWithTolerance(
83 const basegfx::B2DPolygon& rPolygon,
84 double fDiscreteHitTolerance)
86 basegfx::B2DPolygon aLocalPolygon(rPolygon);
87 aLocalPolygon.transform(getViewInformation2D().getObjectToViewTransformation());
89 // get discrete range
90 basegfx::B2DRange aPolygonRange(aLocalPolygon.getB2DRange());
92 if(basegfx::fTools::more(fDiscreteHitTolerance, 0.0))
94 aPolygonRange.grow(fDiscreteHitTolerance);
97 // do rough range test first
98 if(aPolygonRange.isInside(getDiscreteHitPosition()))
100 // check if a polygon edge is hit
101 return basegfx::tools::isInEpsilonRange(
102 aLocalPolygon,
103 getDiscreteHitPosition(),
104 fDiscreteHitTolerance);
107 return false;
110 bool HitTestProcessor2D::checkFillHitWithTolerance(
111 const basegfx::B2DPolyPolygon& rPolyPolygon,
112 double fDiscreteHitTolerance)
114 bool bRetval(false);
115 basegfx::B2DPolyPolygon aLocalPolyPolygon(rPolyPolygon);
116 aLocalPolyPolygon.transform(getViewInformation2D().getObjectToViewTransformation());
118 // get discrete range
119 basegfx::B2DRange aPolygonRange(aLocalPolyPolygon.getB2DRange());
120 const bool bDiscreteHitToleranceUsed(basegfx::fTools::more(fDiscreteHitTolerance, 0.0));
122 if(bDiscreteHitToleranceUsed)
124 aPolygonRange.grow(fDiscreteHitTolerance);
127 // do rough range test first
128 if(aPolygonRange.isInside(getDiscreteHitPosition()))
130 // if a HitTolerance is given, check for polygon edge hit in epsilon first
131 if(bDiscreteHitToleranceUsed &&
132 basegfx::tools::isInEpsilonRange(
133 aLocalPolyPolygon,
134 getDiscreteHitPosition(),
135 fDiscreteHitTolerance))
137 bRetval = true;
140 // check for hit in filled polyPolygon
141 if(!bRetval && basegfx::tools::isInside(
142 aLocalPolyPolygon,
143 getDiscreteHitPosition(),
144 true))
146 bRetval = true;
150 return bRetval;
153 void HitTestProcessor2D::check3DHit(const primitive2d::ScenePrimitive2D& rCandidate)
155 // calculate relative point in unified 2D scene
156 const basegfx::B2DPoint aLogicHitPosition(getViewInformation2D().getInverseObjectToViewTransformation() * getDiscreteHitPosition());
158 // use bitmap check in ScenePrimitive2D
159 bool bTryFastResult(false);
161 if(rCandidate.tryToCheckLastVisualisationDirectHit(aLogicHitPosition, bTryFastResult))
163 mbHit = bTryFastResult;
165 else
167 basegfx::B2DHomMatrix aInverseSceneTransform(rCandidate.getObjectTransformation());
168 aInverseSceneTransform.invert();
169 const basegfx::B2DPoint aRelativePoint(aInverseSceneTransform * aLogicHitPosition);
171 // check if test point is inside scene's unified area at all
172 if(aRelativePoint.getX() >= 0.0 && aRelativePoint.getX() <= 1.0
173 && aRelativePoint.getY() >= 0.0 && aRelativePoint.getY() <= 1.0)
175 // get 3D view information
176 const geometry::ViewInformation3D& rObjectViewInformation3D = rCandidate.getViewInformation3D();
178 // create HitPoint Front and Back, transform to object coordinates
179 basegfx::B3DHomMatrix aViewToObject(rObjectViewInformation3D.getObjectToView());
180 aViewToObject.invert();
181 const basegfx::B3DPoint aFront(aViewToObject * basegfx::B3DPoint(aRelativePoint.getX(), aRelativePoint.getY(), 0.0));
182 const basegfx::B3DPoint aBack(aViewToObject * basegfx::B3DPoint(aRelativePoint.getX(), aRelativePoint.getY(), 1.0));
184 if(!aFront.equal(aBack))
186 const primitive3d::Primitive3DSequence& rPrimitives = rCandidate.getChildren3D();
188 if(rPrimitives.hasElements())
190 // make BoundVolume empty and overlapping test for speedup
191 const basegfx::B3DRange aObjectRange(
192 drawinglayer::primitive3d::getB3DRangeFromPrimitive3DSequence(
193 rPrimitives, rObjectViewInformation3D));
195 if(!aObjectRange.isEmpty())
197 const basegfx::B3DRange aFrontBackRange(aFront, aBack);
199 if(aObjectRange.overlaps(aFrontBackRange))
201 // bound volumes hit, geometric cut tests needed
202 drawinglayer::processor3d::CutFindProcessor aCutFindProcessor(
203 rObjectViewInformation3D,
204 aFront,
205 aBack,
206 true);
207 aCutFindProcessor.process(rPrimitives);
209 mbHit = (0 != aCutFindProcessor.getCutPoints().size());
216 // This is needed to check hit with 3D shadows, too. HitTest is without shadow
217 // to keep compatible with previous versions. Keeping here as reference
219 // if(!getHit())
220 // {
221 // // if scene has shadow, check hit with shadow, too
222 // const primitive2d::Primitive2DSequence xExtracted2DSceneShadow(rCandidate.getShadow2D(getViewInformation2D()));
224 // if(xExtracted2DSceneShadow.hasElements())
225 // {
226 // // process extracted 2D content
227 // process(xExtracted2DSceneShadow);
228 // }
229 // }
231 if(!getHit())
233 // empty 3D scene; Check for border hit
234 basegfx::B2DPolygon aOutline(basegfx::tools::createUnitPolygon());
235 aOutline.transform(rCandidate.getObjectTransformation());
237 mbHit = checkHairlineHitWithTolerance(aOutline, getDiscreteHitTolerance());
240 // This is what the previous version did. Keeping it here for reference
242 // // 2D Scene primitive containing 3D stuff; extract 2D contour in world coordinates
243 // // This may be refined later to an own 3D HitTest renderer which processes the 3D
244 // // geometry directly
245 // const primitive2d::ScenePrimitive2D& rScenePrimitive2DCandidate(static_cast< const primitive2d::ScenePrimitive2D& >(rCandidate));
246 // const primitive2d::Primitive2DSequence xExtracted2DSceneGeometry(rScenePrimitive2DCandidate.getGeometry2D());
247 // const primitive2d::Primitive2DSequence xExtracted2DSceneShadow(rScenePrimitive2DCandidate.getShadow2D(getViewInformation2D()));
249 // if(xExtracted2DSceneGeometry.hasElements() || xExtracted2DSceneShadow.hasElements())
250 // {
251 // // process extracted 2D content
252 // process(xExtracted2DSceneGeometry);
253 // process(xExtracted2DSceneShadow);
254 // }
255 // else
256 // {
257 // // empty 3D scene; Check for border hit
258 // const basegfx::B2DRange aRange(rCandidate.getB2DRange(getViewInformation2D()));
259 // if(!aRange.isEmpty())
260 // {
261 // const basegfx::B2DPolygon aOutline(basegfx::tools::createPolygonFromRect(aRange));
262 // mbHit = checkHairlineHitWithTolerance(aOutline, getDiscreteHitTolerance());
263 // }
264 // }
268 void HitTestProcessor2D::processBasePrimitive2D(const primitive2d::BasePrimitive2D& rCandidate)
270 if(getHit())
272 // stop processing as soon as a hit was recognized
273 return;
276 switch(rCandidate.getPrimitive2DID())
278 case PRIMITIVE2D_ID_TRANSFORMPRIMITIVE2D :
280 // remember current ViewInformation2D
281 const primitive2d::TransformPrimitive2D& rTransformCandidate(static_cast< const primitive2d::TransformPrimitive2D& >(rCandidate));
282 const geometry::ViewInformation2D aLastViewInformation2D(getViewInformation2D());
284 // create new local ViewInformation2D containing transformation
285 const geometry::ViewInformation2D aViewInformation2D(
286 getViewInformation2D().getObjectTransformation() * rTransformCandidate.getTransformation(),
287 getViewInformation2D().getViewTransformation(),
288 getViewInformation2D().getViewport(),
289 getViewInformation2D().getVisualizedPage(),
290 getViewInformation2D().getViewTime(),
291 getViewInformation2D().getExtendedInformationSequence());
292 updateViewInformation(aViewInformation2D);
294 // process child content recursively
295 process(rTransformCandidate.getChildren());
297 // restore transformations
298 updateViewInformation(aLastViewInformation2D);
300 break;
302 case PRIMITIVE2D_ID_POLYGONHAIRLINEPRIMITIVE2D :
304 if(!getHitTextOnly())
306 // create hairline in discrete coordinates
307 const primitive2d::PolygonHairlinePrimitive2D& rPolygonCandidate(static_cast< const primitive2d::PolygonHairlinePrimitive2D& >(rCandidate));
309 // use hairline test
310 mbHit = checkHairlineHitWithTolerance(rPolygonCandidate.getB2DPolygon(), getDiscreteHitTolerance());
313 break;
315 case PRIMITIVE2D_ID_POLYGONMARKERPRIMITIVE2D :
317 if(!getHitTextOnly())
319 // handle marker like hairline; no need to decompose in dashes
320 const primitive2d::PolygonMarkerPrimitive2D& rPolygonCandidate(static_cast< const primitive2d::PolygonMarkerPrimitive2D& >(rCandidate));
322 // use hairline test
323 mbHit = checkHairlineHitWithTolerance(rPolygonCandidate.getB2DPolygon(), getDiscreteHitTolerance());
326 break;
328 case PRIMITIVE2D_ID_POLYGONSTROKEPRIMITIVE2D :
330 if(!getHitTextOnly())
332 // handle stroke evtl. directly; no need to decompose to filled polygon outlines
333 const primitive2d::PolygonStrokePrimitive2D& rPolygonCandidate(static_cast< const primitive2d::PolygonStrokePrimitive2D& >(rCandidate));
334 const attribute::LineAttribute& rLineAttribute = rPolygonCandidate.getLineAttribute();
336 if(basegfx::fTools::more(rLineAttribute.getWidth(), 0.0))
338 if(basegfx::B2DLINEJOIN_MITER == rLineAttribute.getLineJoin())
340 // if line is mitered, use decomposition since mitered line
341 // geometry may use more space than the geometry grown by half line width
342 process(rCandidate.get2DDecomposition(getViewInformation2D()));
344 else
346 // for all other B2DLINEJOIN_* do a hairline HitTest with expanded tolerance
347 const basegfx::B2DVector aDiscreteHalfLineVector(getViewInformation2D().getObjectToViewTransformation()
348 * basegfx::B2DVector(rLineAttribute.getWidth() * 0.5, 0.0));
349 mbHit = checkHairlineHitWithTolerance(
350 rPolygonCandidate.getB2DPolygon(),
351 getDiscreteHitTolerance() + aDiscreteHalfLineVector.getLength());
354 else
356 // hairline; fallback to hairline test. Do not decompose
357 // since this may decompose the hairline to dashes
358 mbHit = checkHairlineHitWithTolerance(rPolygonCandidate.getB2DPolygon(), getDiscreteHitTolerance());
362 break;
364 case PRIMITIVE2D_ID_POLYGONWAVEPRIMITIVE2D :
366 if(!getHitTextOnly())
368 // do not use decompose; just handle like a line with width
369 const primitive2d::PolygonWavePrimitive2D& rPolygonCandidate(static_cast< const primitive2d::PolygonWavePrimitive2D& >(rCandidate));
370 double fLogicHitTolerance(0.0);
372 // if WaveHeight, grow by it
373 if(basegfx::fTools::more(rPolygonCandidate.getWaveHeight(), 0.0))
375 fLogicHitTolerance += rPolygonCandidate.getWaveHeight();
378 // if line width, grow by it
379 if(basegfx::fTools::more(rPolygonCandidate.getLineAttribute().getWidth(), 0.0))
381 fLogicHitTolerance += rPolygonCandidate.getLineAttribute().getWidth() * 0.5;
384 const basegfx::B2DVector aDiscreteHalfLineVector(getViewInformation2D().getObjectToViewTransformation()
385 * basegfx::B2DVector(fLogicHitTolerance, 0.0));
387 mbHit = checkHairlineHitWithTolerance(
388 rPolygonCandidate.getB2DPolygon(),
389 getDiscreteHitTolerance() + aDiscreteHalfLineVector.getLength());
392 break;
394 case PRIMITIVE2D_ID_POLYPOLYGONCOLORPRIMITIVE2D :
396 if(!getHitTextOnly())
398 // create filled polyPolygon in discrete coordinates
399 const primitive2d::PolyPolygonColorPrimitive2D& rPolygonCandidate(static_cast< const primitive2d::PolyPolygonColorPrimitive2D& >(rCandidate));
401 // use fill hit test
402 mbHit = checkFillHitWithTolerance(rPolygonCandidate.getB2DPolyPolygon(), getDiscreteHitTolerance());
405 break;
407 case PRIMITIVE2D_ID_TRANSPARENCEPRIMITIVE2D :
409 // sub-transparence group
410 const primitive2d::TransparencePrimitive2D& rTransCandidate(static_cast< const primitive2d::TransparencePrimitive2D& >(rCandidate));
412 // Currently the transparence content is not taken into account; only
413 // the children are recursively checked for hit. This may be refined for
414 // parts where the content is completely transparent if needed.
415 process(rTransCandidate.getChildren());
417 break;
419 case PRIMITIVE2D_ID_MASKPRIMITIVE2D :
421 // create mask in discrete coordinates; only recursively continue
422 // with content when HitTest position is inside the mask
423 const primitive2d::MaskPrimitive2D& rMaskCandidate(static_cast< const primitive2d::MaskPrimitive2D& >(rCandidate));
425 // use fill hit test
426 if(checkFillHitWithTolerance(rMaskCandidate.getMask(), getDiscreteHitTolerance()))
428 // recursively HitTest children
429 process(rMaskCandidate.getChildren());
432 break;
434 case PRIMITIVE2D_ID_SCENEPRIMITIVE2D :
436 if(!getHitTextOnly())
438 const primitive2d::ScenePrimitive2D& rScenePrimitive2D(
439 static_cast< const primitive2d::ScenePrimitive2D& >(rCandidate));
440 check3DHit(rScenePrimitive2D);
443 break;
445 case PRIMITIVE2D_ID_WRONGSPELLPRIMITIVE2D :
446 case PRIMITIVE2D_ID_MARKERARRAYPRIMITIVE2D :
447 case PRIMITIVE2D_ID_GRIDPRIMITIVE2D :
448 case PRIMITIVE2D_ID_HELPLINEPRIMITIVE2D :
450 // ignorable primitives
451 break;
453 case PRIMITIVE2D_ID_SHADOWPRIMITIVE2D :
455 // Ignore shadows; we do not want to have shadows hittable.
456 // Remove this one to make shadows hittable on demand.
457 break;
459 case PRIMITIVE2D_ID_TEXTSIMPLEPORTIONPRIMITIVE2D :
460 case PRIMITIVE2D_ID_TEXTDECORATEDPORTIONPRIMITIVE2D :
462 // for text use the BoundRect of the primitive itself
463 const basegfx::B2DRange aRange(rCandidate.getB2DRange(getViewInformation2D()));
465 if(!aRange.isEmpty())
467 const basegfx::B2DPolygon aOutline(basegfx::tools::createPolygonFromRect(aRange));
468 mbHit = checkFillHitWithTolerance(basegfx::B2DPolyPolygon(aOutline), getDiscreteHitTolerance());
471 break;
473 case PRIMITIVE2D_ID_BITMAPPRIMITIVE2D :
475 if(!getHitTextOnly())
477 // The recently added BitmapEx::GetTransparency() makes it easy to extend
478 // the BitmapPrimitive2D HitTest to take the contained BotmapEx and it's
479 // transparency into account
480 const basegfx::B2DRange aRange(rCandidate.getB2DRange(getViewInformation2D()));
482 if(!aRange.isEmpty())
484 const primitive2d::BitmapPrimitive2D& rBitmapCandidate(static_cast< const primitive2d::BitmapPrimitive2D& >(rCandidate));
485 const BitmapEx& rBitmapEx = rBitmapCandidate.getBitmapEx();
486 const Size& rSizePixel(rBitmapEx.GetSizePixel());
488 // When tiled rendering, don't bother with the pixel size of the candidate.
489 if(rSizePixel.Width() && rSizePixel.Height() && !mbTiledRendering)
491 basegfx::B2DHomMatrix aBackTransform(
492 getViewInformation2D().getObjectToViewTransformation() *
493 rBitmapCandidate.getTransform());
494 aBackTransform.invert();
496 const basegfx::B2DPoint aRelativePoint(aBackTransform * getDiscreteHitPosition());
497 const basegfx::B2DRange aUnitRange(0.0, 0.0, 1.0, 1.0);
499 if(aUnitRange.isInside(aRelativePoint))
501 const sal_Int32 nX(basegfx::fround(aRelativePoint.getX() * rSizePixel.Width()));
502 const sal_Int32 nY(basegfx::fround(aRelativePoint.getY() * rSizePixel.Height()));
504 mbHit = (0xff != rBitmapEx.GetTransparency(nX, nY));
507 else
509 // fallback to standard HitTest
510 const basegfx::B2DPolygon aOutline(basegfx::tools::createPolygonFromRect(aRange));
511 mbHit = checkFillHitWithTolerance(basegfx::B2DPolyPolygon(aOutline), getDiscreteHitTolerance());
516 break;
518 case PRIMITIVE2D_ID_METAFILEPRIMITIVE2D :
519 case PRIMITIVE2D_ID_CONTROLPRIMITIVE2D :
520 case PRIMITIVE2D_ID_FILLGRADIENTPRIMITIVE2D :
521 case PRIMITIVE2D_ID_FILLHATCHPRIMITIVE2D :
522 case PRIMITIVE2D_ID_PAGEPREVIEWPRIMITIVE2D :
523 case PRIMITIVE2D_ID_MEDIAPRIMITIVE2D:
525 if(!getHitTextOnly())
527 // Class of primitives for which just the BoundRect of the primitive itself
528 // will be used for HitTest currently.
530 // This may be refined in the future, e.g:
531 // - For Bitamps, the mask and/or transparence information may be used
532 // - For MetaFiles, the MetaFile content may be used
533 const basegfx::B2DRange aRange(rCandidate.getB2DRange(getViewInformation2D()));
535 if(!aRange.isEmpty())
537 const basegfx::B2DPolygon aOutline(basegfx::tools::createPolygonFromRect(aRange));
538 mbHit = checkFillHitWithTolerance(basegfx::B2DPolyPolygon(aOutline), getDiscreteHitTolerance());
542 break;
544 case PRIMITIVE2D_ID_HIDDENGEOMETRYPRIMITIVE2D :
546 // HiddenGeometryPrimitive2D; the default decomposition would return an empty seqence,
547 // so force this primitive to process its children directly if the switch is set
548 // (which is the default). Else, ignore invisible content
549 const primitive2d::HiddenGeometryPrimitive2D& rHiddenGeometry(static_cast< const primitive2d::HiddenGeometryPrimitive2D& >(rCandidate));
550 const primitive2d::Primitive2DSequence& rChildren = rHiddenGeometry.getChildren();
552 if(rChildren.hasElements())
554 if(getUseInvisiblePrimitiveContent())
556 process(rChildren);
560 break;
562 case PRIMITIVE2D_ID_POINTARRAYPRIMITIVE2D :
564 if(!getHitTextOnly())
566 const primitive2d::PointArrayPrimitive2D& rPointArrayCandidate(static_cast< const primitive2d::PointArrayPrimitive2D& >(rCandidate));
567 const std::vector< basegfx::B2DPoint >& rPositions = rPointArrayCandidate.getPositions();
568 const sal_uInt32 nCount(rPositions.size());
570 for(sal_uInt32 a(0); !getHit() && a < nCount; a++)
572 const basegfx::B2DPoint aPosition(getViewInformation2D().getObjectToViewTransformation() * rPositions[a]);
573 const basegfx::B2DVector aDistance(aPosition - getDiscreteHitPosition());
575 if(aDistance.getLength() <= getDiscreteHitTolerance())
577 mbHit = true;
582 break;
584 default :
586 // process recursively
587 process(rCandidate.get2DDecomposition(getViewInformation2D()));
589 break;
594 } // end of namespace processor2d
595 } // end of namespace drawinglayer
597 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */