зеркало из https://github.com/mozilla/gecko-dev.git
Bug 988808, part 2 - Convert SVG hit-testing to use Moz2D instead of Thebes backed gfxContext. r=Bas
This commit is contained in:
Родитель
8f52846885
Коммит
3f8bc90bf5
|
@ -8,8 +8,10 @@
|
|||
#include "SVGContentUtils.h"
|
||||
|
||||
// Keep others in (case-insensitive) order:
|
||||
#include "gfx2DGlue.h"
|
||||
#include "gfxMatrix.h"
|
||||
#include "gfxPlatform.h"
|
||||
#include "gfxSVGGlyphs.h"
|
||||
#include "mozilla/gfx/2D.h"
|
||||
#include "mozilla/dom/SVGSVGElement.h"
|
||||
#include "mozilla/RefPtr.h"
|
||||
|
@ -23,9 +25,10 @@
|
|||
#include "nsContentUtils.h"
|
||||
#include "mozilla/gfx/2D.h"
|
||||
#include "mozilla/gfx/Types.h"
|
||||
#include "gfx2DGlue.h"
|
||||
#include "nsStyleContext.h"
|
||||
#include "nsSVGPathDataParser.h"
|
||||
#include "SVGPathData.h"
|
||||
#include "SVGPathElement.h"
|
||||
|
||||
using namespace mozilla;
|
||||
using namespace mozilla::dom;
|
||||
|
@ -58,6 +61,179 @@ SVGContentUtils::ActivateByHyperlink(nsIContent *aContent)
|
|||
static_cast<SVGAnimationElement*>(aContent)->ActivateByHyperlink();
|
||||
}
|
||||
|
||||
enum DashState {
|
||||
eDashedStroke,
|
||||
eContinuousStroke, //< all dashes, no gaps
|
||||
eNoStroke //< all gaps, no dashes
|
||||
};
|
||||
|
||||
static DashState
|
||||
GetStrokeDashData(SVGContentUtils::AutoStrokeOptions* aStrokeOptions,
|
||||
nsSVGElement* aElement,
|
||||
const nsStyleSVG* aStyleSVG,
|
||||
gfxTextContextPaint *aContextPaint)
|
||||
{
|
||||
size_t dashArrayLength;
|
||||
Float totalLengthOfDashes = 0.0, totalLengthOfGaps = 0.0;
|
||||
|
||||
if (aContextPaint && aStyleSVG->mStrokeDasharrayFromObject) {
|
||||
const FallibleTArray<gfxFloat>& dashSrc = aContextPaint->GetStrokeDashArray();
|
||||
dashArrayLength = dashSrc.Length();
|
||||
if (dashArrayLength <= 0) {
|
||||
return eContinuousStroke;
|
||||
}
|
||||
Float* dashPattern = aStrokeOptions->InitDashPattern(dashArrayLength);
|
||||
if (!dashPattern) {
|
||||
return eContinuousStroke;
|
||||
}
|
||||
for (size_t i = 0; i < dashArrayLength; i++) {
|
||||
if (dashSrc[i] < 0.0) {
|
||||
return eContinuousStroke; // invalid
|
||||
}
|
||||
dashPattern[i] = Float(dashSrc[i]);
|
||||
(i % 2 ? totalLengthOfGaps : totalLengthOfDashes) += dashSrc[i];
|
||||
}
|
||||
} else {
|
||||
const nsStyleCoord *dasharray = aStyleSVG->mStrokeDasharray;
|
||||
dashArrayLength = aStyleSVG->mStrokeDasharrayLength;
|
||||
if (dashArrayLength <= 0) {
|
||||
return eContinuousStroke;
|
||||
}
|
||||
Float pathScale = 1.0;
|
||||
if (aElement->Tag() == nsGkAtoms::path) {
|
||||
pathScale = static_cast<SVGPathElement*>(aElement)->
|
||||
GetPathLengthScale(SVGPathElement::eForStroking);
|
||||
if (pathScale <= 0) {
|
||||
return eContinuousStroke;
|
||||
}
|
||||
}
|
||||
Float* dashPattern = aStrokeOptions->InitDashPattern(dashArrayLength);
|
||||
if (!dashPattern) {
|
||||
return eContinuousStroke;
|
||||
}
|
||||
for (uint32_t i = 0; i < dashArrayLength; i++) {
|
||||
Float dashLength =
|
||||
SVGContentUtils::CoordToFloat(aElement, dasharray[i]) * pathScale;
|
||||
if (dashLength < 0.0) {
|
||||
return eContinuousStroke; // invalid
|
||||
}
|
||||
dashPattern[i] = dashLength;
|
||||
(i % 2 ? totalLengthOfGaps : totalLengthOfDashes) += dashLength;
|
||||
}
|
||||
}
|
||||
|
||||
// Now that aStrokeOptions.mDashPattern is fully initialized we can safely
|
||||
// set mDashLength:
|
||||
aStrokeOptions->mDashLength = dashArrayLength;
|
||||
|
||||
if (totalLengthOfDashes <= 0 || totalLengthOfGaps <= 0) {
|
||||
if (totalLengthOfGaps > 0 && totalLengthOfDashes <= 0) {
|
||||
return eNoStroke;
|
||||
}
|
||||
return eContinuousStroke;
|
||||
}
|
||||
|
||||
if (aContextPaint && aStyleSVG->mStrokeDashoffsetFromObject) {
|
||||
aStrokeOptions->mDashOffset = Float(aContextPaint->GetStrokeDashOffset());
|
||||
} else {
|
||||
aStrokeOptions->mDashOffset =
|
||||
SVGContentUtils::CoordToFloat(aElement, aStyleSVG->mStrokeDashoffset);
|
||||
}
|
||||
|
||||
return eDashedStroke;
|
||||
}
|
||||
|
||||
void
|
||||
SVGContentUtils::GetStrokeOptions(AutoStrokeOptions* aStrokeOptions,
|
||||
nsSVGElement* aElement,
|
||||
nsStyleContext* aStyleContext,
|
||||
gfxTextContextPaint *aContextPaint)
|
||||
{
|
||||
nsRefPtr<nsStyleContext> styleContext;
|
||||
if (aStyleContext) {
|
||||
styleContext = aStyleContext;
|
||||
} else {
|
||||
styleContext =
|
||||
nsComputedDOMStyle::GetStyleContextForElementNoFlush(aElement, nullptr,
|
||||
nullptr);
|
||||
}
|
||||
|
||||
if (!styleContext) {
|
||||
return;
|
||||
}
|
||||
|
||||
const nsStyleSVG* styleSVG = styleContext->StyleSVG();
|
||||
|
||||
DashState dashState =
|
||||
GetStrokeDashData(aStrokeOptions, aElement, styleSVG, aContextPaint);
|
||||
|
||||
if (dashState == eNoStroke) {
|
||||
// Hopefully this will shortcircuit any stroke operations:
|
||||
aStrokeOptions->mLineWidth = 0;
|
||||
return;
|
||||
}
|
||||
if (dashState == eContinuousStroke) {
|
||||
// Prevent our caller from wasting time looking at the dash array:
|
||||
aStrokeOptions->mDashLength = 0;
|
||||
}
|
||||
|
||||
aStrokeOptions->mLineWidth =
|
||||
GetStrokeWidth(aElement, styleContext, aContextPaint);
|
||||
|
||||
aStrokeOptions->mMiterLimit = Float(styleSVG->mStrokeMiterlimit);
|
||||
|
||||
switch (styleSVG->mStrokeLinejoin) {
|
||||
case NS_STYLE_STROKE_LINEJOIN_MITER:
|
||||
aStrokeOptions->mLineJoin = JoinStyle::MITER;
|
||||
break;
|
||||
case NS_STYLE_STROKE_LINEJOIN_ROUND:
|
||||
aStrokeOptions->mLineJoin = JoinStyle::ROUND;
|
||||
break;
|
||||
case NS_STYLE_STROKE_LINEJOIN_BEVEL:
|
||||
aStrokeOptions->mLineJoin = JoinStyle::BEVEL;
|
||||
break;
|
||||
}
|
||||
|
||||
switch (styleSVG->mStrokeLinecap) {
|
||||
case NS_STYLE_STROKE_LINECAP_BUTT:
|
||||
aStrokeOptions->mLineCap = CapStyle::BUTT;
|
||||
break;
|
||||
case NS_STYLE_STROKE_LINECAP_ROUND:
|
||||
aStrokeOptions->mLineCap = CapStyle::ROUND;
|
||||
break;
|
||||
case NS_STYLE_STROKE_LINECAP_SQUARE:
|
||||
aStrokeOptions->mLineCap = CapStyle::SQUARE;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
Float
|
||||
SVGContentUtils::GetStrokeWidth(nsSVGElement* aElement,
|
||||
nsStyleContext* aStyleContext,
|
||||
gfxTextContextPaint *aContextPaint)
|
||||
{
|
||||
nsRefPtr<nsStyleContext> styleContext;
|
||||
if (aStyleContext) {
|
||||
styleContext = aStyleContext;
|
||||
} else {
|
||||
styleContext =
|
||||
nsComputedDOMStyle::GetStyleContextForElementNoFlush(aElement, nullptr,
|
||||
nullptr);
|
||||
}
|
||||
|
||||
if (!styleContext) {
|
||||
return 0.0f;
|
||||
}
|
||||
|
||||
const nsStyleSVG* styleSVG = styleContext->StyleSVG();
|
||||
|
||||
if (aContextPaint && styleSVG->mStrokeWidthFromObject) {
|
||||
return aContextPaint->GetStrokeWidth();
|
||||
}
|
||||
|
||||
return SVGContentUtils::CoordToFloat(aElement, styleSVG->mStrokeWidth);
|
||||
}
|
||||
|
||||
float
|
||||
SVGContentUtils::GetFontSize(Element *aElement)
|
||||
{
|
||||
|
|
|
@ -10,12 +10,15 @@
|
|||
#define _USE_MATH_DEFINES
|
||||
#include <math.h>
|
||||
|
||||
#include "mozilla/fallible.h"
|
||||
#include "mozilla/gfx/2D.h" // for StrokeOptions
|
||||
#include "mozilla/gfx/Matrix.h"
|
||||
#include "mozilla/RangedPtr.h"
|
||||
#include "nsError.h"
|
||||
#include "nsStringFwd.h"
|
||||
#include "gfx2DGlue.h"
|
||||
|
||||
class gfxTextContextPaint;
|
||||
class nsIContent;
|
||||
class nsIDocument;
|
||||
class nsIFrame;
|
||||
|
@ -58,6 +61,8 @@ IsSVGWhitespace(char16_t aChar)
|
|||
class SVGContentUtils
|
||||
{
|
||||
public:
|
||||
typedef mozilla::gfx::Float Float;
|
||||
typedef mozilla::gfx::StrokeOptions StrokeOptions;
|
||||
typedef mozilla::SVGAnimatedPreserveAspectRatio SVGAnimatedPreserveAspectRatio;
|
||||
typedef mozilla::SVGPreserveAspectRatio SVGPreserveAspectRatio;
|
||||
|
||||
|
@ -76,6 +81,64 @@ public:
|
|||
*/
|
||||
static void ActivateByHyperlink(nsIContent *aContent);
|
||||
|
||||
/**
|
||||
* Moz2D's StrokeOptions requires someone else to own its mDashPattern
|
||||
* buffer, which is a pain when you want to initialize a StrokeOptions object
|
||||
* in a helper function and pass it out. This sub-class owns the mDashPattern
|
||||
* buffer so that consumers of such a helper function don't need to worry
|
||||
* about creating it, passing it in, or deleting it. (An added benefit is
|
||||
* that in the typical case when stroke-dasharray is short it will avoid
|
||||
* allocating.)
|
||||
*/
|
||||
struct AutoStrokeOptions : public StrokeOptions {
|
||||
AutoStrokeOptions()
|
||||
{
|
||||
MOZ_ASSERT(mDashLength == 0, "InitDashPattern() depends on this");
|
||||
}
|
||||
~AutoStrokeOptions() {
|
||||
if (mDashPattern && mDashPattern != mSmallArray) {
|
||||
delete [] mDashPattern;
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Creates the buffer to store the stroke-dasharray, assuming out-of-memory
|
||||
* does not occur. The buffer's address is assigned to mDashPattern and
|
||||
* returned to the caller as a non-const pointer (so that the caller can
|
||||
* initialize the values in the buffer, since mDashPattern is const).
|
||||
*/
|
||||
Float* InitDashPattern(size_t aDashCount) {
|
||||
if (aDashCount <= MOZ_ARRAY_LENGTH(mSmallArray)) {
|
||||
mDashPattern = mSmallArray;
|
||||
return mSmallArray;
|
||||
}
|
||||
static const mozilla::fallible_t fallible = mozilla::fallible_t();
|
||||
Float* nonConstArray = new (fallible) Float[aDashCount];
|
||||
mDashPattern = nonConstArray;
|
||||
return nonConstArray;
|
||||
}
|
||||
private:
|
||||
// Most dasharrays will fit in this and save us allocating
|
||||
Float mSmallArray[16];
|
||||
};
|
||||
|
||||
static void GetStrokeOptions(AutoStrokeOptions* aStrokeOptions,
|
||||
nsSVGElement* aElement,
|
||||
nsStyleContext* aStyleContext,
|
||||
gfxTextContextPaint *aContextPaint);
|
||||
|
||||
/**
|
||||
* Returns the current computed value of the CSS property 'stroke-width' for
|
||||
* the given element. aStyleContext may be provided as an optimization.
|
||||
* aContextPaint is also optional.
|
||||
*
|
||||
* Note that this function does NOT take account of the value of the 'stroke'
|
||||
* and 'stroke-opacity' properties to, say, return zero if they are "none" or
|
||||
* "0", respectively.
|
||||
*/
|
||||
static Float GetStrokeWidth(nsSVGElement* aElement,
|
||||
nsStyleContext* aStyleContext,
|
||||
gfxTextContextPaint *aContextPaint);
|
||||
|
||||
/*
|
||||
* Get the number of CSS px (user units) per em (i.e. the em-height in user
|
||||
* units) for an nsIContent
|
||||
|
|
|
@ -394,7 +394,7 @@ SVGPathElement::BuildPath(PathBuilder* aBuilder)
|
|||
// opacity here.
|
||||
if (style->mStrokeLinecap == NS_STYLE_STROKE_LINECAP_SQUARE) {
|
||||
strokeLineCap = style->mStrokeLinecap;
|
||||
strokeWidth = GetStrokeWidth();
|
||||
strokeWidth = SVGContentUtils::GetStrokeWidth(this, styleContext, nullptr);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -111,15 +111,3 @@ nsSVGPathGeometryElement::GetFillRule()
|
|||
|
||||
return fillRule;
|
||||
}
|
||||
|
||||
Float
|
||||
nsSVGPathGeometryElement::GetStrokeWidth()
|
||||
{
|
||||
nsRefPtr<nsStyleContext> styleContext =
|
||||
nsComputedDOMStyle::GetStyleContextForElementNoFlush(this, nullptr,
|
||||
nullptr);
|
||||
return styleContext ?
|
||||
SVGContentUtils::CoordToFloat(this,
|
||||
styleContext->StyleSVG()->mStrokeWidth) :
|
||||
0.0f;
|
||||
}
|
||||
|
|
|
@ -77,14 +77,6 @@ public:
|
|||
* this element.
|
||||
*/
|
||||
FillRule GetFillRule();
|
||||
|
||||
/**
|
||||
* Returns the current computed value of the CSS property 'stroke-width' for
|
||||
* this element. (I.e. this does NOT take account of the value of the
|
||||
* 'stroke' and 'stroke-opacity' properties to, say, return zero if they are
|
||||
* "none" or "0", respectively.)
|
||||
*/
|
||||
Float GetStrokeWidth();
|
||||
};
|
||||
|
||||
#endif
|
||||
|
|
|
@ -22,7 +22,7 @@ function run()
|
|||
var originY = div.offsetTop;
|
||||
var circle = document.getElementById("circle");
|
||||
|
||||
var elementFromPoint = document.elementFromPoint(originX + 150, originY + 51);
|
||||
var elementFromPoint = document.elementFromPoint(originX + 150, originY + 52);
|
||||
is(elementFromPoint, circle, "Top of circle should hit");
|
||||
|
||||
var elementFromPoint = document.elementFromPoint(originX + 249, originY + 150);
|
||||
|
@ -44,10 +44,10 @@ function run()
|
|||
<div id="content">
|
||||
|
||||
<div width="100%" height="1" id="div">
|
||||
</div>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="100%" height="400" id="svg">
|
||||
<circle id="circle" cx="1.5" cy="1.5" r="1" transform="scale(100, 100)"/>
|
||||
</svg>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<pre id="test">
|
||||
|
|
|
@ -7,6 +7,7 @@
|
|||
#include "nsSVGPathGeometryFrame.h"
|
||||
|
||||
// Keep others in (case-insensitive) order:
|
||||
#include "gfx2DGlue.h"
|
||||
#include "gfxContext.h"
|
||||
#include "gfxPlatform.h"
|
||||
#include "gfxSVGGlyphs.h"
|
||||
|
@ -20,6 +21,7 @@
|
|||
#include "nsSVGUtils.h"
|
||||
#include "mozilla/ArrayUtils.h"
|
||||
#include "SVGAnimatedTransformList.h"
|
||||
#include "SVGContentUtils.h"
|
||||
#include "SVGGraphicsElement.h"
|
||||
|
||||
using namespace mozilla;
|
||||
|
@ -234,48 +236,77 @@ nsSVGPathGeometryFrame::PaintSVG(nsRenderingContext *aContext,
|
|||
nsIFrame*
|
||||
nsSVGPathGeometryFrame::GetFrameForPoint(const nsPoint &aPoint)
|
||||
{
|
||||
gfxMatrix canvasTM = GetCanvasTM(FOR_HIT_TESTING);
|
||||
if (canvasTM.IsSingular()) {
|
||||
gfxMatrix hitTestingTM = GetCanvasTM(FOR_HIT_TESTING);
|
||||
if (hitTestingTM.IsSingular()) {
|
||||
return nullptr;
|
||||
}
|
||||
uint16_t fillRule, hitTestFlags;
|
||||
FillRule fillRule;
|
||||
uint16_t hitTestFlags;
|
||||
if (GetStateBits() & NS_STATE_SVG_CLIPPATH_CHILD) {
|
||||
hitTestFlags = SVG_HIT_TEST_FILL;
|
||||
fillRule = StyleSVG()->mClipRule;
|
||||
fillRule = StyleSVG()->mClipRule == NS_STYLE_FILL_RULE_NONZERO
|
||||
? FillRule::FILL_WINDING : FillRule::FILL_EVEN_ODD;
|
||||
} else {
|
||||
hitTestFlags = GetHitTestFlags();
|
||||
nsPoint point =
|
||||
nsSVGUtils::TransformOuterSVGPointToChildFrame(aPoint, canvasTM, PresContext());
|
||||
nsSVGUtils::TransformOuterSVGPointToChildFrame(aPoint, hitTestingTM, PresContext());
|
||||
if (!hitTestFlags || ((hitTestFlags & SVG_HIT_TEST_CHECK_MRECT) &&
|
||||
!mRect.Contains(point)))
|
||||
!mRect.Contains(point))) {
|
||||
return nullptr;
|
||||
fillRule = StyleSVG()->mFillRule;
|
||||
}
|
||||
fillRule = StyleSVG()->mFillRule == NS_STYLE_FILL_RULE_NONZERO
|
||||
? FillRule::FILL_WINDING : FillRule::FILL_EVEN_ODD;
|
||||
}
|
||||
|
||||
bool isHit = false;
|
||||
|
||||
nsRefPtr<gfxContext> tmpCtx =
|
||||
new gfxContext(gfxPlatform::GetPlatform()->ScreenReferenceSurface());
|
||||
nsSVGPathGeometryElement* content =
|
||||
static_cast<nsSVGPathGeometryElement*>(mContent);
|
||||
|
||||
GeneratePath(tmpCtx, ToMatrix(canvasTM));
|
||||
gfxPoint userSpacePoint =
|
||||
tmpCtx->DeviceToUser(gfxPoint(aPoint.x, aPoint.y) / PresContext()->AppUnitsPerCSSPixel());
|
||||
// Using ScreenReferenceDrawTarget() opens us to Moz2D backend specific hit-
|
||||
// testing bugs. Maybe we should use a BackendType::CAIRO DT for hit-testing
|
||||
// so that we get more consistent/backwards compatible results?
|
||||
RefPtr<DrawTarget> drawTarget =
|
||||
gfxPlatform::GetPlatform()->ScreenReferenceDrawTarget();
|
||||
RefPtr<PathBuilder> builder =
|
||||
drawTarget->CreatePathBuilder(fillRule);
|
||||
RefPtr<Path> path = content->BuildPath(builder);
|
||||
if (!path) {
|
||||
return nullptr; // no path, so we don't paint anything that can be hit
|
||||
}
|
||||
|
||||
if (fillRule == NS_STYLE_FILL_RULE_EVENODD)
|
||||
tmpCtx->SetFillRule(gfxContext::FILL_RULE_EVEN_ODD);
|
||||
else
|
||||
tmpCtx->SetFillRule(gfxContext::FILL_RULE_WINDING);
|
||||
if (!hitTestingTM.IsIdentity()) {
|
||||
// We'll only get here if we don't have a nsDisplayItem that has called us
|
||||
// (for example, if we're a NS_FRAME_IS_NONDISPLAY frame under a clipPath).
|
||||
RefPtr<PathBuilder> builder =
|
||||
path->TransformedCopyToBuilder(ToMatrix(hitTestingTM), fillRule);
|
||||
path = builder->Finish();
|
||||
}
|
||||
|
||||
if (hitTestFlags & SVG_HIT_TEST_FILL)
|
||||
isHit = tmpCtx->PointInFill(userSpacePoint);
|
||||
int32_t appUnitsPerCSSPx = PresContext()->AppUnitsPerCSSPixel();
|
||||
Point userSpacePoint = Point(Float(aPoint.x) / appUnitsPerCSSPx,
|
||||
Float(aPoint.y) / appUnitsPerCSSPx);
|
||||
|
||||
if (hitTestFlags & SVG_HIT_TEST_FILL) {
|
||||
isHit = path->ContainsPoint(userSpacePoint, Matrix());
|
||||
}
|
||||
if (!isHit && (hitTestFlags & SVG_HIT_TEST_STROKE)) {
|
||||
nsSVGUtils::SetupCairoStrokeGeometry(this, tmpCtx);
|
||||
// tmpCtx's matrix may have transformed by SetupCairoStrokeGeometry
|
||||
// if there is a non-scaling stroke. We need to transform userSpacePoint
|
||||
// so that everything is using the same co-ordinate system.
|
||||
userSpacePoint =
|
||||
nsSVGUtils::GetStrokeTransform(this).Invert().Transform(userSpacePoint);
|
||||
isHit = tmpCtx->PointInStroke(userSpacePoint);
|
||||
SVGContentUtils::AutoStrokeOptions stroke;
|
||||
SVGContentUtils::GetStrokeOptions(&stroke, content, StyleContext(), nullptr);
|
||||
Matrix nonScalingStrokeMatrix =
|
||||
ToMatrix(nsSVGUtils::GetStrokeTransform(this));
|
||||
if (!nonScalingStrokeMatrix.IsIdentity()) {
|
||||
// We need to transform the path back into the appropriate ancestor
|
||||
// coordinate system in order for non-scaled stroke to be correct.
|
||||
// Naturally we also need to transform the point into the same
|
||||
// coordinate system in order to hit-test against the path.
|
||||
nonScalingStrokeMatrix.Invert();
|
||||
userSpacePoint = ToMatrix(hitTestingTM) * nonScalingStrokeMatrix * userSpacePoint;
|
||||
RefPtr<PathBuilder> builder =
|
||||
path->TransformedCopyToBuilder(nonScalingStrokeMatrix, fillRule);
|
||||
path = builder->Finish();
|
||||
}
|
||||
isHit = path->StrokeContainsPoint(stroke, userSpacePoint, Matrix());
|
||||
}
|
||||
|
||||
if (isHit && nsSVGUtils::HitTestClip(this, aPoint))
|
||||
|
|
Загрузка…
Ссылка в новой задаче