LibreOffice Module drawinglayer (master) 1
hittestprocessor2d.cxx
Go to the documentation of this file.
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
37#include <comphelper/lok.hxx>
39
41{
43 const basegfx::B2DPoint& rLogicHitPosition,
44 const basegfx::B2DVector& rLogicHitTolerancePerAxis,
45 bool bHitTextOnly)
46 : BaseProcessor2D(rViewInformation),
47 maDiscreteHitTolerancePerAxis(rLogicHitTolerancePerAxis),
48 mbCollectHitStack(false),
49 mbHit(false),
50 mbHitTextOnly(bHitTextOnly)
51 {
52 // ensure input parameters for hit tolerance is >= 0.0
57
59 {
60 // generate discrete hit tolerance
62 = getViewInformation2D().getObjectToViewTransformation() * rLogicHitTolerancePerAxis;
63 }
64
65 // generate discrete hit position
67 }
68
70 {
71 }
72
74 const basegfx::B2DPolygon& rPolygon,
75 const basegfx::B2DVector& rDiscreteHitTolerancePerAxis) const
76 {
77 basegfx::B2DPolygon aLocalPolygon(rPolygon);
78 aLocalPolygon.transform(getViewInformation2D().getObjectToViewTransformation());
79
80 // get discrete range
81 basegfx::B2DRange aPolygonRange(aLocalPolygon.getB2DRange());
82
83 if(rDiscreteHitTolerancePerAxis.getX() > 0 || rDiscreteHitTolerancePerAxis.getY() > 0)
84 {
85 aPolygonRange.grow(rDiscreteHitTolerancePerAxis);
86 }
87
88 // do rough range test first
89 if(aPolygonRange.isInside(getDiscreteHitPosition()))
90 {
91 // check if a polygon edge is hit
93 aLocalPolygon,
95 std::max(rDiscreteHitTolerancePerAxis.getX(), rDiscreteHitTolerancePerAxis.getY()));
96 }
97
98 return false;
99 }
100
102 const basegfx::B2DPolyPolygon& rPolyPolygon,
103 const basegfx::B2DVector& rDiscreteHitTolerancePerAxis) const
104 {
105 bool bRetval(false);
106 basegfx::B2DPolyPolygon aLocalPolyPolygon(rPolyPolygon);
107 aLocalPolyPolygon.transform(getViewInformation2D().getObjectToViewTransformation());
108
109 // get discrete range
110 basegfx::B2DRange aPolygonRange(aLocalPolyPolygon.getB2DRange());
111
112 const bool bDiscreteHitToleranceUsed(rDiscreteHitTolerancePerAxis.getX() > 0
113 || rDiscreteHitTolerancePerAxis.getY() > 0);
114
115 if (bDiscreteHitToleranceUsed)
116 {
117 aPolygonRange.grow(rDiscreteHitTolerancePerAxis);
118 }
119
120 // do rough range test first
121 if(aPolygonRange.isInside(getDiscreteHitPosition()))
122 {
123 // if a HitTolerance is given, check for polygon edge hit in epsilon first
124 if(bDiscreteHitToleranceUsed &&
126 aLocalPolyPolygon,
128 std::max(rDiscreteHitTolerancePerAxis.getX(), rDiscreteHitTolerancePerAxis.getY())))
129 {
130 bRetval = true;
131 }
132
133 // check for hit in filled polyPolygon
134 if(!bRetval && basegfx::utils::isInside(
135 aLocalPolyPolygon,
137 true))
138 {
139 bRetval = true;
140 }
141 }
142
143 return bRetval;
144 }
145
147 {
148 // calculate relative point in unified 2D scene
149 const basegfx::B2DPoint aLogicHitPosition(getViewInformation2D().getInverseObjectToViewTransformation() * getDiscreteHitPosition());
150
151 // use bitmap check in ScenePrimitive2D
152 bool bTryFastResult(false);
153
154 if(rCandidate.tryToCheckLastVisualisationDirectHit(aLogicHitPosition, bTryFastResult))
155 {
156 mbHit = bTryFastResult;
157 }
158 else
159 {
160 basegfx::B2DHomMatrix aInverseSceneTransform(rCandidate.getObjectTransformation());
161 aInverseSceneTransform.invert();
162 const basegfx::B2DPoint aRelativePoint(aInverseSceneTransform * aLogicHitPosition);
163
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)
167 {
168 // get 3D view information
169 const geometry::ViewInformation3D& rObjectViewInformation3D = rCandidate.getViewInformation3D();
170
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));
176
177 if(!aFront.equal(aBack))
178 {
179 const primitive3d::Primitive3DContainer& rPrimitives = rCandidate.getChildren3D();
180
181 if(!rPrimitives.empty())
182 {
183 // make BoundVolume empty and overlapping test for speedup
184 const basegfx::B3DRange aObjectRange(
185 rPrimitives.getB3DRange(rObjectViewInformation3D));
186
187 if(!aObjectRange.isEmpty())
188 {
189 const basegfx::B3DRange aFrontBackRange(aFront, aBack);
190
191 if(aObjectRange.overlaps(aFrontBackRange))
192 {
193 // bound volumes hit, geometric cut tests needed
195 rObjectViewInformation3D,
196 aFront,
197 aBack,
198 true);
199 aCutFindProcessor.process(rPrimitives);
200
201 mbHit = (!aCutFindProcessor.getCutPoints().empty());
202 }
203 }
204 }
205 }
206 }
207
208 if(!getHit())
209 {
210 // empty 3D scene; Check for border hit
212 aOutline.transform(rCandidate.getObjectTransformation());
213
215 }
216 }
217 }
218
220 {
221 if(getHit())
222 {
223 // stop processing as soon as a hit was recognized
224 return;
225 }
226
227 switch(rCandidate.getPrimitive2DID())
228 {
230 {
231 // remember current ViewInformation2D
232 const primitive2d::TransformPrimitive2D& rTransformCandidate(static_cast< const primitive2d::TransformPrimitive2D& >(rCandidate));
233 const geometry::ViewInformation2D aLastViewInformation2D(getViewInformation2D());
234
235 // create new local ViewInformation2D containing transformation
237 aViewInformation2D.setObjectTransformation(getViewInformation2D().getObjectTransformation() * rTransformCandidate.getTransformation());
238 updateViewInformation(aViewInformation2D);
239
240 // process child content recursively
241 process(rTransformCandidate.getChildren());
242
243 // restore transformations
244 updateViewInformation(aLastViewInformation2D);
245
246 break;
247 }
249 {
250 if(!getHitTextOnly())
251 {
252 // create hairline in discrete coordinates
253 const primitive2d::PolygonHairlinePrimitive2D& rPolygonCandidate(static_cast< const primitive2d::PolygonHairlinePrimitive2D& >(rCandidate));
254
255 // use hairline test
257 }
258
259 break;
260 }
262 {
263 if(!getHitTextOnly())
264 {
265 // handle marker like hairline; no need to decompose in dashes
266 const primitive2d::PolygonMarkerPrimitive2D& rPolygonCandidate(static_cast< const primitive2d::PolygonMarkerPrimitive2D& >(rCandidate));
267
268 // use hairline test
270 }
271
272 break;
273 }
275 {
276 if(!getHitTextOnly())
277 {
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();
281
282 if(basegfx::fTools::more(rLineAttribute.getWidth(), 0.0))
283 {
284 if(basegfx::B2DLineJoin::Miter == rLineAttribute.getLineJoin())
285 {
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);
289 }
290 else
291 {
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, rLineAttribute.getWidth() * 0.5));
296 rPolygonCandidate.getB2DPolygon(),
297 getDiscreteHitTolerance() + aDiscreteHalfLineVector);
298 }
299 }
300 else
301 {
302 // hairline; fallback to hairline test. Do not decompose
303 // since this may decompose the hairline to dashes
305 }
306 }
307
308 break;
309 }
311 {
312 if(!getHitTextOnly())
313 {
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);
317
318 // if WaveHeight, grow by it
319 if(basegfx::fTools::more(rPolygonCandidate.getWaveHeight(), 0.0))
320 {
321 fLogicHitTolerance += rPolygonCandidate.getWaveHeight();
322 }
323
324 // if line width, grow by it
325 if(basegfx::fTools::more(rPolygonCandidate.getLineAttribute().getWidth(), 0.0))
326 {
327 fLogicHitTolerance += rPolygonCandidate.getLineAttribute().getWidth() * 0.5;
328 }
329
330 const basegfx::B2DVector aDiscreteHalfLineVector(getViewInformation2D().getObjectToViewTransformation()
331 * basegfx::B2DVector(fLogicHitTolerance, fLogicHitTolerance));
332
334 rPolygonCandidate.getB2DPolygon(),
335 getDiscreteHitTolerance() + aDiscreteHalfLineVector);
336 }
337
338 break;
339 }
341 {
342 if(!getHitTextOnly())
343 {
344 // create filled polyPolygon in discrete coordinates
345 const primitive2d::PolyPolygonColorPrimitive2D& rPolygonCandidate(static_cast< const primitive2d::PolyPolygonColorPrimitive2D& >(rCandidate));
346
347 // use fill hit test
349 }
350
351 break;
352 }
354 {
355 // sub-transparence group
356 const primitive2d::TransparencePrimitive2D& rTransCandidate(static_cast< const primitive2d::TransparencePrimitive2D& >(rCandidate));
357
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());
362
363 break;
364 }
366 {
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));
370
371 // use fill hit test
373 {
374 // recursively HitTest children
375 process(rMaskCandidate.getChildren());
376 }
377
378 break;
379 }
381 {
382 if(!getHitTextOnly())
383 {
384 const primitive2d::ScenePrimitive2D& rScenePrimitive2D(
385 static_cast< const primitive2d::ScenePrimitive2D& >(rCandidate));
386 check3DHit(rScenePrimitive2D);
387 }
388
389 break;
390 }
395 {
396 // ignorable primitives
397 break;
398 }
400 {
401 // Ignore shadows; we do not want to have shadows hittable.
402 // Remove this one to make shadows hittable on demand.
403 break;
404 }
407 {
408 // for text use the BoundRect of the primitive itself
409 const basegfx::B2DRange aRange(rCandidate.getB2DRange(getViewInformation2D()));
410
411 if(!aRange.isEmpty())
412 {
415 }
416
417 break;
418 }
420 {
421 if(!getHitTextOnly())
422 {
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()));
427
428 if(!aRange.isEmpty())
429 {
430 const primitive2d::BitmapPrimitive2D& rBitmapCandidate(static_cast< const primitive2d::BitmapPrimitive2D& >(rCandidate));
431 const BitmapEx aBitmapEx(rBitmapCandidate.getBitmap());
432 const Size& rSizePixel(aBitmapEx.GetSizePixel());
433
434 // When tiled rendering, don't bother with the pixel size of the candidate.
435 if(rSizePixel.Width() && rSizePixel.Height() && !comphelper::LibreOfficeKit::isActive())
436 {
437 basegfx::B2DHomMatrix aBackTransform(
438 getViewInformation2D().getObjectToViewTransformation() *
439 rBitmapCandidate.getTransform());
440 aBackTransform.invert();
441
442 const basegfx::B2DPoint aRelativePoint(aBackTransform * getDiscreteHitPosition());
443 const basegfx::B2DRange aUnitRange(0.0, 0.0, 1.0, 1.0);
444
445 if(aUnitRange.isInside(aRelativePoint))
446 {
447 const sal_Int32 nX(basegfx::fround(aRelativePoint.getX() * rSizePixel.Width()));
448 const sal_Int32 nY(basegfx::fround(aRelativePoint.getY() * rSizePixel.Height()));
449
450 mbHit = (0 != aBitmapEx.GetAlpha(nX, nY));
451 }
452 }
453 else
454 {
455 // fallback to standard HitTest
458 }
459 }
460 }
461
462 break;
463 }
471 {
472 if(!getHitTextOnly())
473 {
474 // Class of primitives for which just the BoundRect of the primitive itself
475 // will be used for HitTest currently.
476 //
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()));
481
482 if(!aRange.isEmpty())
483 {
486 }
487 }
488
489 break;
490 }
492 {
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();
498
499 if(!rChildren.empty())
500 {
501 process(rChildren);
502 }
503
504 break;
505 }
507 {
508 if(!getHitTextOnly())
509 {
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());
513
514 for(sal_uInt32 a(0); !getHit() && a < nCount; a++)
515 {
516 const basegfx::B2DPoint aPosition(getViewInformation2D().getObjectToViewTransformation() * rPositions[a]);
518
519 if (aDistance.getLength() <= std::max(getDiscreteHitTolerance().getX(),
520 getDiscreteHitTolerance().getY()))
521 {
522 mbHit = true;
523 }
524 }
525 }
526
527 break;
528 }
529 default :
530 {
531 // process recursively
532 process(rCandidate);
533
534 break;
535 }
536 }
537
538 if (getHit() && getCollectHitStack())
539 {
543 }
544 }
545
546} // end of namespace
547
548/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
sal_uInt8 GetAlpha(sal_Int32 nX, sal_Int32 nY) const
const Size & GetSizePixel() const
constexpr tools::Long Height() const
constexpr tools::Long Width() const
void transform(const basegfx::B2DHomMatrix &rMatrix)
B2DRange getB2DRange() const
void transform(const basegfx::B2DHomMatrix &rMatrix)
B2DRange const & getB2DRange() const
bool overlaps(const B3DRange &rRange) const
bool isEmpty() const
bool equal(const B3DTuple &rTup) const
void grow(TYPE fValue)
bool isInside(const Tuple2D< TYPE > &rTuple) const
bool isEmpty() const
bool equalZero() const
TYPE getX() const
void setY(TYPE fY)
TYPE getY() const
void setX(TYPE fX)
basegfx::B2DLineJoin getLineJoin() const
void setObjectTransformation(const basegfx::B2DHomMatrix &rNew)
const basegfx::B2DHomMatrix & getObjectToViewTransformation() const
On-demand prepared Object to View transformation and its inverse for convenience.
const basegfx::B3DHomMatrix & getObjectToView() const
for convenience, the linear combination of the above four transformations is offered
virtual sal_uInt32 getPrimitive2DID() const =0
provide unique ID for fast identifying of known primitive implementations in renderers.
virtual basegfx::B2DRange getB2DRange(const geometry::ViewInformation2D &rViewInformation) const
The default implementation will use getDecomposition results to create the range.
const basegfx::B2DHomMatrix & getTransform() const
const BitmapEx & getBitmap() const
data read access
const Primitive2DContainer & getChildren() const
data read access
const basegfx::B2DPolyPolygon & getMask() const
data read access
const std::vector< basegfx::B2DPoint > & getPositions() const
data read access
const basegfx::B2DPolyPolygon & getB2DPolyPolygon() const
data read access
const basegfx::B2DPolygon & getB2DPolygon() const
data read access
const basegfx::B2DPolygon & getB2DPolygon() const
data read access
const basegfx::B2DPolygon & getB2DPolygon() const
data read access
const attribute::LineAttribute & getLineAttribute() const
const geometry::ViewInformation3D & getViewInformation3D() const
const primitive3d::Primitive3DContainer & getChildren3D() const
data read access
bool tryToCheckLastVisualisationDirectHit(const basegfx::B2DPoint &rLogicHitPoint, bool &o_rResult) const
Fast HitTest which uses the last buffered BitmapEx from the last rendered area if available.
const basegfx::B2DHomMatrix & getObjectTransformation() const
const basegfx::B2DHomMatrix & getTransformation() const
data read access
basegfx::B3DRange getB3DRange(const geometry::ViewInformation3D &aViewInformation) const
void process(const primitive2d::BasePrimitive2D &rCandidate)
void updateViewInformation(const geometry::ViewInformation2D &rViewInformation2D)
const geometry::ViewInformation2D & getViewInformation2D() const
data read access
bool checkFillHitWithTolerance(const basegfx::B2DPolyPolygon &rPolyPolygon, const basegfx::B2DVector &rDiscreteHitTolerancePerAxis) const
const basegfx::B2DPoint & getDiscreteHitPosition() const
data read access
basegfx::B2DVector maDiscreteHitTolerancePerAxis
discrete HitTolerance
bool checkHairlineHitWithTolerance(const basegfx::B2DPolygon &rPolygon, const basegfx::B2DVector &rDiscreteHitTolerancePerAxis) const
HitTestProcessor2D(const geometry::ViewInformation2D &rViewInformation, const basegfx::B2DPoint &rLogicHitPosition, const basegfx::B2DVector &rLogicHitTolerancePerAxis, bool bHitTextOnly)
void processBasePrimitive2D(const primitive2d::BasePrimitive2D &rCandidate) override
tooling methods
basegfx::B2DPoint maDiscreteHitPosition
discrete HitTest position
const basegfx::B2DVector & getDiscreteHitTolerance() const
primitive2d::Primitive2DContainer maHitStack
stack of HitPrimitives, taken care of during HitTest run
void check3DHit(const primitive2d::ScenePrimitive2D &rCandidate)
bool mbHit
Boolean to flag if a hit was found. If yes, fast exit is taken.
void process(const primitive3d::Primitive3DContainer &rSource)
const ::std::vector< basegfx::B3DPoint > & getCutPoints() const
data read access
int nCount
#define PRIMITIVE2D_ID_TEXTDECORATEDPORTIONPRIMITIVE2D
#define PRIMITIVE2D_ID_FILLGRADIENTPRIMITIVE2D
#define PRIMITIVE2D_ID_SCENEPRIMITIVE2D
#define PRIMITIVE2D_ID_FILLGRAPHICPRIMITIVE2D
#define PRIMITIVE2D_ID_CONTROLPRIMITIVE2D
#define PRIMITIVE2D_ID_GRIDPRIMITIVE2D
#define PRIMITIVE2D_ID_POLYGONMARKERPRIMITIVE2D
#define PRIMITIVE2D_ID_TRANSFORMPRIMITIVE2D
#define PRIMITIVE2D_ID_HIDDENGEOMETRYPRIMITIVE2D
#define PRIMITIVE2D_ID_POLYPOLYGONCOLORPRIMITIVE2D
#define PRIMITIVE2D_ID_POLYGONHAIRLINEPRIMITIVE2D
#define PRIMITIVE2D_ID_TRANSPARENCEPRIMITIVE2D
#define PRIMITIVE2D_ID_HELPLINEPRIMITIVE2D
#define PRIMITIVE2D_ID_POLYGONWAVEPRIMITIVE2D
#define PRIMITIVE2D_ID_FILLHATCHPRIMITIVE2D
#define PRIMITIVE2D_ID_POINTARRAYPRIMITIVE2D
#define PRIMITIVE2D_ID_SHADOWPRIMITIVE2D
#define PRIMITIVE2D_ID_PAGEPREVIEWPRIMITIVE2D
#define PRIMITIVE2D_ID_WRONGSPELLPRIMITIVE2D
#define PRIMITIVE2D_ID_MASKPRIMITIVE2D
#define PRIMITIVE2D_ID_POLYGONSTROKEPRIMITIVE2D
#define PRIMITIVE2D_ID_METAFILEPRIMITIVE2D
#define PRIMITIVE2D_ID_BITMAPPRIMITIVE2D
#define PRIMITIVE2D_ID_MEDIAPRIMITIVE2D
#define PRIMITIVE2D_ID_MARKERARRAYPRIMITIVE2D
#define PRIMITIVE2D_ID_TEXTSIMPLEPORTIONPRIMITIVE2D
uno_Any a
bool more(const T &rfValA, const T &rfValB)
B2DPolygon createPolygonFromRect(const B2DRectangle &rRect, double fRadiusX, double fRadiusY)
bool isInside(const B2DPolygon &rCandidate, const B2DPoint &rPoint, bool bWithBorder)
bool isInEpsilonRange(const B2DPoint &rEdgeStart, const B2DPoint &rEdgeEnd, const B2DPoint &rTestPosition, double fDistance)
B2DPolygon const & createUnitPolygon()
B2IRange fround(const B2DRange &rRange)
o3tl::enumarray< SvxBoxItemLine, sal_uInt16 > aDistance