Kaydet (Commit) d62d07b3 authored tarafından Armin Le Grand's avatar Armin Le Grand

Corrected HitTest for layouted text

For text layouted using EditEngine the HitTest in SVX is
identifying Field like URLs. Thus ist is better to use the
anyways more precise primitives for HitTest (rotation/shear/
mirror, ...). This was necessary since the former mechanism
which used a combination of primitive-beased HitTest and then
using an Outliner to get the position/content of the Field
landed on different positions e.g. when the layout needed to
use multiple lines for the contained URL, but there could
be more cases found.
Adapted the text decompositon, the primitive HitTest and
the TextHirearchyFieldPrimitive2D accordingly.

Change-Id: Ice559e20d02547fdcfcf9783e7cc5481706aab03
Reviewed-on: https://gerrit.libreoffice.org/40591Tested-by: 's avatarJenkins <ci@libreoffice.org>
Reviewed-by: 's avatarArmin Le Grand <Armin.Le.Grand@cib.de>
üst 22334f8a
...@@ -95,11 +95,28 @@ namespace drawinglayer ...@@ -95,11 +95,28 @@ namespace drawinglayer
TextHierarchyFieldPrimitive2D::TextHierarchyFieldPrimitive2D( TextHierarchyFieldPrimitive2D::TextHierarchyFieldPrimitive2D(
const Primitive2DContainer& rChildren, const Primitive2DContainer& rChildren,
const FieldType& rFieldType, const FieldType& rFieldType,
const OUString& rString) const std::vector< std::pair< OUString, OUString>>* pNameValue)
: GroupPrimitive2D(rChildren), : GroupPrimitive2D(rChildren),
meType(rFieldType), meType(rFieldType),
maString(rString) meNameValue()
{ {
if (nullptr != pNameValue)
{
meNameValue = *pNameValue;
}
}
OUString TextHierarchyFieldPrimitive2D::getValue(const OUString& rName) const
{
for (const std::pair< OUString, OUString >& candidate : meNameValue)
{
if (candidate.first.equals(rName))
{
return candidate.second;
}
}
return OUString();
} }
bool TextHierarchyFieldPrimitive2D::operator==(const BasePrimitive2D& rPrimitive) const bool TextHierarchyFieldPrimitive2D::operator==(const BasePrimitive2D& rPrimitive) const
...@@ -109,7 +126,7 @@ namespace drawinglayer ...@@ -109,7 +126,7 @@ namespace drawinglayer
const TextHierarchyFieldPrimitive2D& rCompare = static_cast<const TextHierarchyFieldPrimitive2D&>(rPrimitive); const TextHierarchyFieldPrimitive2D& rCompare = static_cast<const TextHierarchyFieldPrimitive2D&>(rPrimitive);
return (getType() == rCompare.getType() return (getType() == rCompare.getType()
&& getString() == rCompare.getString()); && getNameValue() == rCompare.getNameValue());
} }
return false; return false;
......
...@@ -45,6 +45,8 @@ namespace drawinglayer ...@@ -45,6 +45,8 @@ namespace drawinglayer
: BaseProcessor2D(rViewInformation), : BaseProcessor2D(rViewInformation),
maDiscreteHitPosition(), maDiscreteHitPosition(),
mfDiscreteHitTolerance(0.0), mfDiscreteHitTolerance(0.0),
maHitStack(),
mbCollectHitStack(false),
mbHit(false), mbHit(false),
mbHitTextOnly(bHitTextOnly) mbHitTextOnly(bHitTextOnly)
{ {
...@@ -536,6 +538,13 @@ namespace drawinglayer ...@@ -536,6 +538,13 @@ namespace drawinglayer
break; break;
} }
} }
if (getHit() && getCollectHitStack())
{
/// push candidate to HitStack to create it. This only happens when a hit is found and
/// creating the HitStack was requested (see collectHitStack)
maHitStack.append(primitive2d::Primitive2DReference(const_cast< primitive2d::BasePrimitive2D* >(&rCandidate)));
}
} }
} // end of namespace processor2d } // end of namespace processor2d
......
...@@ -977,6 +977,7 @@ namespace drawinglayer ...@@ -977,6 +977,7 @@ namespace drawinglayer
const OString aCommentStringCommon("FIELD_SEQ_BEGIN"); const OString aCommentStringCommon("FIELD_SEQ_BEGIN");
const OString aCommentStringPage("FIELD_SEQ_BEGIN;PageField"); const OString aCommentStringPage("FIELD_SEQ_BEGIN;PageField");
const OString aCommentStringEnd("FIELD_SEQ_END"); const OString aCommentStringEnd("FIELD_SEQ_END");
OUString aURL;
switch(rFieldPrimitive.getType()) switch(rFieldPrimitive.getType())
{ {
...@@ -992,8 +993,13 @@ namespace drawinglayer ...@@ -992,8 +993,13 @@ namespace drawinglayer
} }
case drawinglayer::primitive2d::FIELD_TYPE_URL : case drawinglayer::primitive2d::FIELD_TYPE_URL :
{ {
const OUString& rURL = rFieldPrimitive.getString(); aURL = rFieldPrimitive.getValue("URL");
mpMetaFile->AddAction(new MetaCommentAction(aCommentStringCommon, 0, reinterpret_cast< const sal_uInt8* >(rURL.getStr()), 2 * rURL.getLength()));
if (!aURL.isEmpty())
{
mpMetaFile->AddAction(new MetaCommentAction(aCommentStringCommon, 0, reinterpret_cast<const sal_uInt8*>(aURL.getStr()), 2 * aURL.getLength()));
}
break; break;
} }
} }
...@@ -1015,7 +1021,7 @@ namespace drawinglayer ...@@ -1015,7 +1021,7 @@ namespace drawinglayer
(sal_Int32)ceil(aViewRange.getMaxX()), (sal_Int32)ceil(aViewRange.getMaxY())); (sal_Int32)ceil(aViewRange.getMaxX()), (sal_Int32)ceil(aViewRange.getMaxY()));
vcl::PDFExtOutDevBookmarkEntry aBookmark; vcl::PDFExtOutDevBookmarkEntry aBookmark;
aBookmark.nLinkId = mpPDFExtOutDevData->CreateLink(aRectLogic); aBookmark.nLinkId = mpPDFExtOutDevData->CreateLink(aRectLogic);
aBookmark.aBookmark = rFieldPrimitive.getString(); aBookmark.aBookmark = aURL;
std::vector< vcl::PDFExtOutDevBookmarkEntry >& rBookmarks = mpPDFExtOutDevData->GetBookmarks(); std::vector< vcl::PDFExtOutDevBookmarkEntry >& rBookmarks = mpPDFExtOutDevData->GetBookmarks();
rBookmarks.push_back( aBookmark ); rBookmarks.push_back( aBookmark );
} }
......
...@@ -148,19 +148,23 @@ namespace drawinglayer ...@@ -148,19 +148,23 @@ namespace drawinglayer
class DRAWINGLAYER_DLLPUBLIC TextHierarchyFieldPrimitive2D : public GroupPrimitive2D class DRAWINGLAYER_DLLPUBLIC TextHierarchyFieldPrimitive2D : public GroupPrimitive2D
{ {
private: private:
/// field type definition
FieldType meType; FieldType meType;
OUString maString;
/// field data as name/value pairs (dependent of field type definition)
std::vector< std::pair< OUString, OUString>> meNameValue;
public: public:
/// constructor /// constructor
TextHierarchyFieldPrimitive2D( TextHierarchyFieldPrimitive2D(
const Primitive2DContainer& rChildren, const Primitive2DContainer& rChildren,
const FieldType& rFieldType, const FieldType& rFieldType,
const OUString& rString); const std::vector< std::pair< OUString, OUString>>* pNameValue = nullptr);
/// data read access /// data read access
FieldType getType() const { return meType; } FieldType getType() const { return meType; }
const OUString& getString() const { return maString; } const std::vector< std::pair< OUString, OUString>>& getNameValue() const { return meNameValue; }
OUString getValue(const OUString& rName) const;
/// compare operator /// compare operator
virtual bool operator==(const BasePrimitive2D& rPrimitive) const override; virtual bool operator==(const BasePrimitive2D& rPrimitive) const override;
......
...@@ -46,6 +46,13 @@ namespace drawinglayer ...@@ -46,6 +46,13 @@ namespace drawinglayer
/// discrete HitTolerance /// discrete HitTolerance
double mfDiscreteHitTolerance; double mfDiscreteHitTolerance;
/// stack of HitPrimitives, taken care of during HitTest run
primitive2d::Primitive2DContainer maHitStack;
/// flag if HitStack shall be collected as part of the result, default is false
bool mbCollectHitStack : 1;
/// Boolean to flag if a hit was found. If yes, fast exit is taken
bool mbHit : 1; bool mbHit : 1;
/// flag to concentrate on text hits only /// flag to concentrate on text hits only
...@@ -69,9 +76,17 @@ namespace drawinglayer ...@@ -69,9 +76,17 @@ namespace drawinglayer
bool bHitTextOnly); bool bHitTextOnly);
virtual ~HitTestProcessor2D() override; virtual ~HitTestProcessor2D() override;
/// switch on collecting primitives for a found hit on maHitStack, default is off
void collectHitStack(bool bCollect) { mbCollectHitStack = bCollect; }
/// get HitStack of primitives, first is the one that created the hit, last is the
/// top-most
const primitive2d::Primitive2DContainer& getHitStack() const { return maHitStack; }
/// data read access /// data read access
const basegfx::B2DPoint& getDiscreteHitPosition() const { return maDiscreteHitPosition; } const basegfx::B2DPoint& getDiscreteHitPosition() const { return maDiscreteHitPosition; }
double getDiscreteHitTolerance() const { return mfDiscreteHitTolerance; } double getDiscreteHitTolerance() const { return mfDiscreteHitTolerance; }
bool getCollectHitStack() const { return mbCollectHitStack; }
bool getHit() const { return mbHit; } bool getHit() const { return mbHit; }
bool getHitTextOnly() const { return mbHitTextOnly; } bool getHitTextOnly() const { return mbHitTextOnly; }
}; };
......
...@@ -32,6 +32,7 @@ class SdrLayerIDSet; ...@@ -32,6 +32,7 @@ class SdrLayerIDSet;
class SdrObjList; class SdrObjList;
namespace sdr { namespace contact { class ViewObjectContact; }} namespace sdr { namespace contact { class ViewObjectContact; }}
namespace basegfx { class B2DPoint; } namespace basegfx { class B2DPoint; }
namespace drawinglayer { namespace primitive2d { class Primitive2DContainer; }}
// Wrappers for classic Sdr* Mode/View classes // Wrappers for classic Sdr* Mode/View classes
...@@ -42,7 +43,9 @@ SVX_DLLPUBLIC SdrObject* SdrObjectPrimitiveHit( ...@@ -42,7 +43,9 @@ SVX_DLLPUBLIC SdrObject* SdrObjectPrimitiveHit(
sal_uInt16 nTol, sal_uInt16 nTol,
const SdrPageView& rSdrPageView, const SdrPageView& rSdrPageView,
const SdrLayerIDSet* pVisiLayer, const SdrLayerIDSet* pVisiLayer,
bool bTextOnly); bool bTextOnly,
/// allow getting back an evtl. resulting primitive stack which lead to a hit
drawinglayer::primitive2d::Primitive2DContainer* pHitContainer = nullptr);
SVX_DLLPUBLIC SdrObject* SdrObjListPrimitiveHit( SVX_DLLPUBLIC SdrObject* SdrObjListPrimitiveHit(
const SdrObjList& rList, const SdrObjList& rList,
...@@ -59,7 +62,9 @@ SVX_DLLPUBLIC bool ViewObjectContactPrimitiveHit( ...@@ -59,7 +62,9 @@ SVX_DLLPUBLIC bool ViewObjectContactPrimitiveHit(
const sdr::contact::ViewObjectContact& rVOC, const sdr::contact::ViewObjectContact& rVOC,
const basegfx::B2DPoint& rHitPosition, const basegfx::B2DPoint& rHitPosition,
double fLogicHitTolerance, double fLogicHitTolerance,
bool bTextOnly); bool bTextOnly,
/// allow to get back the stack of primitives that lead to the hit
drawinglayer::primitive2d::Primitive2DContainer* pHitContainer = nullptr);
#endif // INCLUDED_SVX_SDRHITTESTHELPER_HXX #endif // INCLUDED_SVX_SDRHITTESTHELPER_HXX
......
...@@ -39,7 +39,8 @@ SdrObject* SdrObjectPrimitiveHit( ...@@ -39,7 +39,8 @@ SdrObject* SdrObjectPrimitiveHit(
sal_uInt16 nTol, sal_uInt16 nTol,
const SdrPageView& rSdrPageView, const SdrPageView& rSdrPageView,
const SdrLayerIDSet* pVisiLayer, const SdrLayerIDSet* pVisiLayer,
bool bTextOnly) bool bTextOnly,
drawinglayer::primitive2d::Primitive2DContainer* pHitContainer)
{ {
SdrObject* pResult = nullptr; SdrObject* pResult = nullptr;
...@@ -77,7 +78,7 @@ SdrObject* SdrObjectPrimitiveHit( ...@@ -77,7 +78,7 @@ SdrObject* SdrObjectPrimitiveHit(
const sdr::contact::ViewObjectContact& rVOC = rObject.GetViewContact().GetViewObjectContact( const sdr::contact::ViewObjectContact& rVOC = rObject.GetViewContact().GetViewObjectContact(
rSdrPageView.GetPageWindow(0)->GetObjectContact()); rSdrPageView.GetPageWindow(0)->GetObjectContact());
if(ViewObjectContactPrimitiveHit(rVOC, aHitPosition, fLogicTolerance, bTextOnly)) if(ViewObjectContactPrimitiveHit(rVOC, aHitPosition, fLogicTolerance, bTextOnly, pHitContainer))
{ {
pResult = const_cast< SdrObject* >(&rObject); pResult = const_cast< SdrObject* >(&rObject);
} }
...@@ -117,7 +118,8 @@ bool ViewObjectContactPrimitiveHit( ...@@ -117,7 +118,8 @@ bool ViewObjectContactPrimitiveHit(
const sdr::contact::ViewObjectContact& rVOC, const sdr::contact::ViewObjectContact& rVOC,
const basegfx::B2DPoint& rHitPosition, const basegfx::B2DPoint& rHitPosition,
double fLogicHitTolerance, double fLogicHitTolerance,
bool bTextOnly) bool bTextOnly,
drawinglayer::primitive2d::Primitive2DContainer* pHitContainer)
{ {
basegfx::B2DRange aObjectRange(rVOC.getObjectRange()); basegfx::B2DRange aObjectRange(rVOC.getObjectRange());
...@@ -146,11 +148,23 @@ bool ViewObjectContactPrimitiveHit( ...@@ -146,11 +148,23 @@ bool ViewObjectContactPrimitiveHit(
fLogicHitTolerance, fLogicHitTolerance,
bTextOnly); bTextOnly);
// ask for HitStack
aHitTestProcessor2D.collectHitStack(true);
// feed it with the primitives // feed it with the primitives
aHitTestProcessor2D.process(rSequence); aHitTestProcessor2D.process(rSequence);
// deliver result // deliver result
return aHitTestProcessor2D.getHit(); if (aHitTestProcessor2D.getHit())
{
if (pHitContainer)
{
// fetch HitStack primitives if requested
*pHitContainer = aHitTestProcessor2D.getHitStack();
}
return true;
}
} }
} }
} }
......
...@@ -479,15 +479,22 @@ namespace ...@@ -479,15 +479,22 @@ namespace
if(pURLField) if(pURLField)
{ {
pPrimitive = new drawinglayer::primitive2d::TextHierarchyFieldPrimitive2D(aSequence, drawinglayer::primitive2d::FIELD_TYPE_URL, pURLField->GetURL()); // extended this to hold more of the contents of the original
// SvxURLField since that stuff is still used in HitTest and e.g. Calc
std::vector< std::pair< OUString, OUString>> meValues;
meValues.push_back(std::pair< OUString, OUString>("URL", pURLField->GetURL()));
meValues.push_back(std::pair< OUString, OUString>("Representation", pURLField->GetRepresentation()));
meValues.push_back(std::pair< OUString, OUString>("TargetFrame", pURLField->GetTargetFrame()));
meValues.push_back(std::pair< OUString, OUString>("SvxURLFormat", OUString::number(static_cast<sal_uInt16>(pURLField->GetFormat()))));
pPrimitive = new drawinglayer::primitive2d::TextHierarchyFieldPrimitive2D(aSequence, drawinglayer::primitive2d::FIELD_TYPE_URL, &meValues);
} }
else if(pPageField) else if(pPageField)
{ {
pPrimitive = new drawinglayer::primitive2d::TextHierarchyFieldPrimitive2D(aSequence, drawinglayer::primitive2d::FIELD_TYPE_PAGE, ""); pPrimitive = new drawinglayer::primitive2d::TextHierarchyFieldPrimitive2D(aSequence, drawinglayer::primitive2d::FIELD_TYPE_PAGE);
} }
else else
{ {
pPrimitive = new drawinglayer::primitive2d::TextHierarchyFieldPrimitive2D(aSequence, drawinglayer::primitive2d::FIELD_TYPE_COMMON, ""); pPrimitive = new drawinglayer::primitive2d::TextHierarchyFieldPrimitive2D(aSequence, drawinglayer::primitive2d::FIELD_TYPE_COMMON);
} }
} }
......
...@@ -50,6 +50,7 @@ ...@@ -50,6 +50,7 @@
#include <svx/sdrhittesthelper.hxx> #include <svx/sdrhittesthelper.hxx>
#include <svx/sdr/contact/viewcontact.hxx> #include <svx/sdr/contact/viewcontact.hxx>
#include <drawinglayer/processor2d/contourextractor2d.hxx> #include <drawinglayer/processor2d/contourextractor2d.hxx>
#include <drawinglayer/primitive2d/texthierarchyprimitive2d.hxx>
SdrViewEvent::SdrViewEvent() SdrViewEvent::SdrViewEvent()
...@@ -440,45 +441,66 @@ SdrHitKind SdrView::PickAnything(const Point& rLogicPos, SdrViewEvent& rVEvt) co ...@@ -440,45 +441,66 @@ SdrHitKind SdrView::PickAnything(const Point& rLogicPos, SdrViewEvent& rVEvt) co
SdrTextObj* pTextObj=dynamic_cast<SdrTextObj*>( pHitObj ); SdrTextObj* pTextObj=dynamic_cast<SdrTextObj*>( pHitObj );
if (pTextObj!=nullptr && pTextObj->HasText()) if (pTextObj!=nullptr && pTextObj->HasText())
{ {
bool bTEHit(pPV && // use the primitive-based HitTest which is more accurate anyways. It
SdrObjectPrimitiveHit(*pTextObj, aLocalLogicPosition, 0, *pPV, &pPV->GetVisibleLayers(), true)); // will correctly handle rotated/mirrored/sheared/scaled text and can
// now return a HitContainer containing the primitive hierarchy of the
if (bTEHit) // primitive that triggered the hit. The first entry is that primitive,
// the others are the full stack of primitives leading to that one which
// includes grouping primitives (like TextHierarchyPrimitives we deed here)
// but also all decomposed ones which lead to the creation of that primitive
drawinglayer::primitive2d::Primitive2DContainer aHitContainer;
const bool bTEHit(pPV && SdrObjectPrimitiveHit(*pTextObj, aLocalLogicPosition, 0, *pPV, &pPV->GetVisibleLayers(), true, &aHitContainer));
if (bTEHit && !aHitContainer.empty())
{ {
tools::Rectangle aTextRect; // search for TextHierarchyFieldPrimitive2D which contains the needed information
tools::Rectangle aAnchor; // about a possible URLField
SdrOutliner* pOutliner = &pTextObj->ImpGetDrawOutliner(); const drawinglayer::primitive2d::TextHierarchyFieldPrimitive2D* pTextHierarchyFieldPrimitive2D = nullptr;
if( pTextObj->GetModel() )
pOutliner = &pTextObj->GetModel()->GetHitTestOutliner(); for (const drawinglayer::primitive2d::Primitive2DReference& xReference : aHitContainer)
{
pTextObj->TakeTextRect( *pOutliner, aTextRect, false, &aAnchor, false ); if (xReference.is())
{
// #i73628# Use a text-relative position for hit test in hit test outliner // try to cast to drawinglayer::primitive2d::TextHierarchyFieldPrimitive2D implementation
Point aTemporaryTextRelativePosition(aLocalLogicPosition - aTextRect.TopLeft()); pTextHierarchyFieldPrimitive2D = dynamic_cast<const drawinglayer::primitive2d::TextHierarchyFieldPrimitive2D*>(xReference.get());
// account for FitToSize if (pTextHierarchyFieldPrimitive2D)
bool bFitToSize(pTextObj->IsFitToSize()); {
if (bFitToSize) { break;
Fraction aX(aTextRect.GetWidth()-1,aAnchor.GetWidth()-1); }
Fraction aY(aTextRect.GetHeight()-1,aAnchor.GetHeight()-1); }
ResizePoint(aTemporaryTextRelativePosition,Point(),aX,aY);
} }
// account for rotation
const GeoStat& rGeo=pTextObj->GetGeoStat(); if (nullptr != pTextHierarchyFieldPrimitive2D)
if (rGeo.nRotationAngle!=0) RotatePoint(aTemporaryTextRelativePosition,Point(),-rGeo.nSin,rGeo.nCos); // -sin for Unrotate
// we currently don't account for ticker text
if(mpActualOutDev && mpActualOutDev->GetOutDevType() == OUTDEV_WINDOW)
{ {
OutlinerView aOLV(pOutliner, static_cast<vcl::Window*>(mpActualOutDev.get())); if (drawinglayer::primitive2d::FieldType::FIELD_TYPE_URL == pTextHierarchyFieldPrimitive2D->getType())
const EditView& aEV=aOLV.GetEditView(); {
const SvxFieldItem* pItem=aEV.GetField(aTemporaryTextRelativePosition); // problem with the old code is that a *pointer* to an instance of
if (pItem!=nullptr) { // SvxURLField is set in the Event which is per se not good since that
const SvxFieldData* pFld=pItem->GetField(); // data comes from a temporary EditEngine's data and could vanish any
const SvxURLField* pURL=dynamic_cast<const SvxURLField*>( pFld ); // moment. Have to replace for now with a static instance that gets
if (pURL!=nullptr) { // filled/initialized from the original data held in the TextHierarchyField-
eHit=SdrHitKind::UrlField; // Primitive2D (see impTextBreakupHandler::impCheckFieldPrimitive).
rVEvt.pURLField=pURL; // Unfortunately things like 'TargetFrame' are still used in Calc, so this
// can currently not get replaced. For the future the Name/Value vector or
// the TextHierarchyFieldPrimitive2D itself should/will be used for handling
// that data
static SvxURLField aSvxURLField;
aSvxURLField.SetURL(pTextHierarchyFieldPrimitive2D->getValue("URL"));
aSvxURLField.SetRepresentation(pTextHierarchyFieldPrimitive2D->getValue("Representation"));
aSvxURLField.SetTargetFrame(pTextHierarchyFieldPrimitive2D->getValue("TargetFrame"));
const OUString aFormat(pTextHierarchyFieldPrimitive2D->getValue("SvxURLFormat"));
if (!aFormat.isEmpty())
{
aSvxURLField.SetFormat(static_cast<SvxURLFormat>(aFormat.toInt32()));
} }
// set HitKind and pointer to local static instance in the Event
// to comply to old stuff
eHit = SdrHitKind::UrlField;
rVEvt.pURLField = &aSvxURLField;
} }
} }
} }
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment