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
TextHierarchyFieldPrimitive2D::TextHierarchyFieldPrimitive2D(
const Primitive2DContainer& rChildren,
const FieldType& rFieldType,
const OUString& rString)
const std::vector< std::pair< OUString, OUString>>* pNameValue)
: GroupPrimitive2D(rChildren),
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
......@@ -109,7 +126,7 @@ namespace drawinglayer
const TextHierarchyFieldPrimitive2D& rCompare = static_cast<const TextHierarchyFieldPrimitive2D&>(rPrimitive);
return (getType() == rCompare.getType()
&& getString() == rCompare.getString());
&& getNameValue() == rCompare.getNameValue());
}
return false;
......
......@@ -45,6 +45,8 @@ namespace drawinglayer
: BaseProcessor2D(rViewInformation),
maDiscreteHitPosition(),
mfDiscreteHitTolerance(0.0),
maHitStack(),
mbCollectHitStack(false),
mbHit(false),
mbHitTextOnly(bHitTextOnly)
{
......@@ -536,6 +538,13 @@ namespace drawinglayer
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
......
......@@ -977,6 +977,7 @@ namespace drawinglayer
const OString aCommentStringCommon("FIELD_SEQ_BEGIN");
const OString aCommentStringPage("FIELD_SEQ_BEGIN;PageField");
const OString aCommentStringEnd("FIELD_SEQ_END");
OUString aURL;
switch(rFieldPrimitive.getType())
{
......@@ -992,8 +993,13 @@ namespace drawinglayer
}
case drawinglayer::primitive2d::FIELD_TYPE_URL :
{
const OUString& rURL = rFieldPrimitive.getString();
mpMetaFile->AddAction(new MetaCommentAction(aCommentStringCommon, 0, reinterpret_cast< const sal_uInt8* >(rURL.getStr()), 2 * rURL.getLength()));
aURL = rFieldPrimitive.getValue("URL");
if (!aURL.isEmpty())
{
mpMetaFile->AddAction(new MetaCommentAction(aCommentStringCommon, 0, reinterpret_cast<const sal_uInt8*>(aURL.getStr()), 2 * aURL.getLength()));
}
break;
}
}
......@@ -1015,7 +1021,7 @@ namespace drawinglayer
(sal_Int32)ceil(aViewRange.getMaxX()), (sal_Int32)ceil(aViewRange.getMaxY()));
vcl::PDFExtOutDevBookmarkEntry aBookmark;
aBookmark.nLinkId = mpPDFExtOutDevData->CreateLink(aRectLogic);
aBookmark.aBookmark = rFieldPrimitive.getString();
aBookmark.aBookmark = aURL;
std::vector< vcl::PDFExtOutDevBookmarkEntry >& rBookmarks = mpPDFExtOutDevData->GetBookmarks();
rBookmarks.push_back( aBookmark );
}
......
......@@ -148,19 +148,23 @@ namespace drawinglayer
class DRAWINGLAYER_DLLPUBLIC TextHierarchyFieldPrimitive2D : public GroupPrimitive2D
{
private:
/// field type definition
FieldType meType;
OUString maString;
/// field data as name/value pairs (dependent of field type definition)
std::vector< std::pair< OUString, OUString>> meNameValue;
public:
/// constructor
TextHierarchyFieldPrimitive2D(
const Primitive2DContainer& rChildren,
const FieldType& rFieldType,
const OUString& rString);
const std::vector< std::pair< OUString, OUString>>* pNameValue = nullptr);
/// data read access
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
virtual bool operator==(const BasePrimitive2D& rPrimitive) const override;
......
......@@ -46,6 +46,13 @@ namespace drawinglayer
/// discrete HitTolerance
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;
/// flag to concentrate on text hits only
......@@ -69,9 +76,17 @@ namespace drawinglayer
bool bHitTextOnly);
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
const basegfx::B2DPoint& getDiscreteHitPosition() const { return maDiscreteHitPosition; }
double getDiscreteHitTolerance() const { return mfDiscreteHitTolerance; }
bool getCollectHitStack() const { return mbCollectHitStack; }
bool getHit() const { return mbHit; }
bool getHitTextOnly() const { return mbHitTextOnly; }
};
......
......@@ -32,6 +32,7 @@ class SdrLayerIDSet;
class SdrObjList;
namespace sdr { namespace contact { class ViewObjectContact; }}
namespace basegfx { class B2DPoint; }
namespace drawinglayer { namespace primitive2d { class Primitive2DContainer; }}
// Wrappers for classic Sdr* Mode/View classes
......@@ -42,7 +43,9 @@ SVX_DLLPUBLIC SdrObject* SdrObjectPrimitiveHit(
sal_uInt16 nTol,
const SdrPageView& rSdrPageView,
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(
const SdrObjList& rList,
......@@ -59,7 +62,9 @@ SVX_DLLPUBLIC bool ViewObjectContactPrimitiveHit(
const sdr::contact::ViewObjectContact& rVOC,
const basegfx::B2DPoint& rHitPosition,
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
......
......@@ -39,7 +39,8 @@ SdrObject* SdrObjectPrimitiveHit(
sal_uInt16 nTol,
const SdrPageView& rSdrPageView,
const SdrLayerIDSet* pVisiLayer,
bool bTextOnly)
bool bTextOnly,
drawinglayer::primitive2d::Primitive2DContainer* pHitContainer)
{
SdrObject* pResult = nullptr;
......@@ -77,7 +78,7 @@ SdrObject* SdrObjectPrimitiveHit(
const sdr::contact::ViewObjectContact& rVOC = rObject.GetViewContact().GetViewObjectContact(
rSdrPageView.GetPageWindow(0)->GetObjectContact());
if(ViewObjectContactPrimitiveHit(rVOC, aHitPosition, fLogicTolerance, bTextOnly))
if(ViewObjectContactPrimitiveHit(rVOC, aHitPosition, fLogicTolerance, bTextOnly, pHitContainer))
{
pResult = const_cast< SdrObject* >(&rObject);
}
......@@ -117,7 +118,8 @@ bool ViewObjectContactPrimitiveHit(
const sdr::contact::ViewObjectContact& rVOC,
const basegfx::B2DPoint& rHitPosition,
double fLogicHitTolerance,
bool bTextOnly)
bool bTextOnly,
drawinglayer::primitive2d::Primitive2DContainer* pHitContainer)
{
basegfx::B2DRange aObjectRange(rVOC.getObjectRange());
......@@ -146,11 +148,23 @@ bool ViewObjectContactPrimitiveHit(
fLogicHitTolerance,
bTextOnly);
// ask for HitStack
aHitTestProcessor2D.collectHitStack(true);
// feed it with the primitives
aHitTestProcessor2D.process(rSequence);
// 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
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)
{
pPrimitive = new drawinglayer::primitive2d::TextHierarchyFieldPrimitive2D(aSequence, drawinglayer::primitive2d::FIELD_TYPE_PAGE, "");
pPrimitive = new drawinglayer::primitive2d::TextHierarchyFieldPrimitive2D(aSequence, drawinglayer::primitive2d::FIELD_TYPE_PAGE);
}
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 @@
#include <svx/sdrhittesthelper.hxx>
#include <svx/sdr/contact/viewcontact.hxx>
#include <drawinglayer/processor2d/contourextractor2d.hxx>
#include <drawinglayer/primitive2d/texthierarchyprimitive2d.hxx>
SdrViewEvent::SdrViewEvent()
......@@ -440,45 +441,66 @@ SdrHitKind SdrView::PickAnything(const Point& rLogicPos, SdrViewEvent& rVEvt) co
SdrTextObj* pTextObj=dynamic_cast<SdrTextObj*>( pHitObj );
if (pTextObj!=nullptr && pTextObj->HasText())
{
bool bTEHit(pPV &&
SdrObjectPrimitiveHit(*pTextObj, aLocalLogicPosition, 0, *pPV, &pPV->GetVisibleLayers(), true));
if (bTEHit)
// use the primitive-based HitTest which is more accurate anyways. It
// will correctly handle rotated/mirrored/sheared/scaled text and can
// now return a HitContainer containing the primitive hierarchy of the
// 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;
tools::Rectangle aAnchor;
SdrOutliner* pOutliner = &pTextObj->ImpGetDrawOutliner();
if( pTextObj->GetModel() )
pOutliner = &pTextObj->GetModel()->GetHitTestOutliner();
pTextObj->TakeTextRect( *pOutliner, aTextRect, false, &aAnchor, false );
// #i73628# Use a text-relative position for hit test in hit test outliner
Point aTemporaryTextRelativePosition(aLocalLogicPosition - aTextRect.TopLeft());
// account for FitToSize
bool bFitToSize(pTextObj->IsFitToSize());
if (bFitToSize) {
Fraction aX(aTextRect.GetWidth()-1,aAnchor.GetWidth()-1);
Fraction aY(aTextRect.GetHeight()-1,aAnchor.GetHeight()-1);
ResizePoint(aTemporaryTextRelativePosition,Point(),aX,aY);
// search for TextHierarchyFieldPrimitive2D which contains the needed information
// about a possible URLField
const drawinglayer::primitive2d::TextHierarchyFieldPrimitive2D* pTextHierarchyFieldPrimitive2D = nullptr;
for (const drawinglayer::primitive2d::Primitive2DReference& xReference : aHitContainer)
{
if (xReference.is())
{
// try to cast to drawinglayer::primitive2d::TextHierarchyFieldPrimitive2D implementation
pTextHierarchyFieldPrimitive2D = dynamic_cast<const drawinglayer::primitive2d::TextHierarchyFieldPrimitive2D*>(xReference.get());
if (pTextHierarchyFieldPrimitive2D)
{
break;
}
}
}
// account for rotation
const GeoStat& rGeo=pTextObj->GetGeoStat();
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)
if (nullptr != pTextHierarchyFieldPrimitive2D)
{
OutlinerView aOLV(pOutliner, static_cast<vcl::Window*>(mpActualOutDev.get()));
const EditView& aEV=aOLV.GetEditView();
const SvxFieldItem* pItem=aEV.GetField(aTemporaryTextRelativePosition);
if (pItem!=nullptr) {
const SvxFieldData* pFld=pItem->GetField();
const SvxURLField* pURL=dynamic_cast<const SvxURLField*>( pFld );
if (pURL!=nullptr) {
eHit=SdrHitKind::UrlField;
rVEvt.pURLField=pURL;
if (drawinglayer::primitive2d::FieldType::FIELD_TYPE_URL == pTextHierarchyFieldPrimitive2D->getType())
{
// problem with the old code is that a *pointer* to an instance of
// SvxURLField is set in the Event which is per se not good since that
// data comes from a temporary EditEngine's data and could vanish any
// moment. Have to replace for now with a static instance that gets
// filled/initialized from the original data held in the TextHierarchyField-
// Primitive2D (see impTextBreakupHandler::impCheckFieldPrimitive).
// 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