diff options
Diffstat (limited to 'dom/svg/SVGGeometryElement.cpp')
-rw-r--r-- | dom/svg/SVGGeometryElement.cpp | 298 |
1 files changed, 298 insertions, 0 deletions
diff --git a/dom/svg/SVGGeometryElement.cpp b/dom/svg/SVGGeometryElement.cpp new file mode 100644 index 0000000000..d012a911b8 --- /dev/null +++ b/dom/svg/SVGGeometryElement.cpp @@ -0,0 +1,298 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "SVGGeometryElement.h" + +#include "DOMSVGPoint.h" +#include "gfxPlatform.h" +#include "nsCOMPtr.h" +#include "SVGAnimatedLength.h" +#include "SVGCircleElement.h" +#include "SVGEllipseElement.h" +#include "SVGGeometryProperty.h" +#include "SVGPathElement.h" +#include "SVGRectElement.h" +#include "mozilla/dom/DOMPointBinding.h" +#include "mozilla/dom/SVGLengthBinding.h" +#include "mozilla/gfx/2D.h" +#include "mozilla/RefPtr.h" +#include "mozilla/StaticPrefs_layout.h" +#include "mozilla/SVGContentUtils.h" + +using namespace mozilla::gfx; + +namespace mozilla::dom { + +SVGElement::NumberInfo SVGGeometryElement::sNumberInfo = {nsGkAtoms::pathLength, + 0}; + +//---------------------------------------------------------------------- +// Implementation + +SVGGeometryElement::SVGGeometryElement( + already_AddRefed<mozilla::dom::NodeInfo>&& aNodeInfo) + : SVGGeometryElementBase(std::move(aNodeInfo)) {} + +SVGElement::NumberAttributesInfo SVGGeometryElement::GetNumberInfo() { + return NumberAttributesInfo(&mPathLength, &sNumberInfo, 1); +} + +void SVGGeometryElement::AfterSetAttr(int32_t aNamespaceID, nsAtom* aName, + const nsAttrValue* aValue, + const nsAttrValue* aOldValue, + nsIPrincipal* aSubjectPrincipal, + bool aNotify) { + if (mCachedPath && aNamespaceID == kNameSpaceID_None && + AttributeDefinesGeometry(aName)) { + mCachedPath = nullptr; + } + return SVGGeometryElementBase::AfterSetAttr( + aNamespaceID, aName, aValue, aOldValue, aSubjectPrincipal, aNotify); +} + +bool SVGGeometryElement::AttributeDefinesGeometry(const nsAtom* aName) { + if (aName == nsGkAtoms::pathLength) { + return true; + } + + // Check for SVGAnimatedLength attribute + LengthAttributesInfo info = GetLengthInfo(); + for (uint32_t i = 0; i < info.mCount; i++) { + if (aName == info.mInfos[i].mName) { + return true; + } + } + + return false; +} + +bool SVGGeometryElement::GeometryDependsOnCoordCtx() { + // Check the SVGAnimatedLength attribute + LengthAttributesInfo info = + const_cast<SVGGeometryElement*>(this)->GetLengthInfo(); + for (uint32_t i = 0; i < info.mCount; i++) { + if (info.mValues[i].IsPercentage()) { + return true; + } + } + return false; +} + +bool SVGGeometryElement::IsMarkable() { return false; } + +void SVGGeometryElement::GetMarkPoints(nsTArray<SVGMark>* aMarks) {} + +already_AddRefed<Path> SVGGeometryElement::GetOrBuildPath( + const DrawTarget* aDrawTarget, FillRule aFillRule) { + // We only cache the path if it matches the backend used for screen painting, + // and it's not a capturing drawtarget. A capturing DT might start using the + // the Path object on a different thread (OMTP), and we might have a data race + // if we keep a handle to it. + bool cacheable = aDrawTarget->GetBackendType() == + gfxPlatform::GetPlatform()->GetDefaultContentBackend(); + + if (cacheable && mCachedPath && mCachedPath->GetFillRule() == aFillRule && + aDrawTarget->GetBackendType() == mCachedPath->GetBackendType()) { + RefPtr<Path> path(mCachedPath); + return path.forget(); + } + RefPtr<PathBuilder> builder = aDrawTarget->CreatePathBuilder(aFillRule); + RefPtr<Path> path = BuildPath(builder); + if (cacheable) { + mCachedPath = path; + } + return path.forget(); +} + +already_AddRefed<Path> SVGGeometryElement::GetOrBuildPathForMeasuring() { + RefPtr<DrawTarget> drawTarget = + gfxPlatform::GetPlatform()->ScreenReferenceDrawTarget(); + FillRule fillRule = mCachedPath ? mCachedPath->GetFillRule() : GetFillRule(); + return GetOrBuildPath(drawTarget, fillRule); +} + +// This helper is currently identical to GetOrBuildPathForMeasuring. +// We keep it a separate method because for length measuring purpose, +// fillRule isn't really needed. Derived class (e.g. SVGPathElement) +// may override GetOrBuildPathForMeasuring() to ignore fillRule. And +// GetOrBuildPathForMeasuring() itself may be modified in the future. +already_AddRefed<Path> SVGGeometryElement::GetOrBuildPathForHitTest() { + RefPtr<DrawTarget> drawTarget = + gfxPlatform::GetPlatform()->ScreenReferenceDrawTarget(); + FillRule fillRule = mCachedPath ? mCachedPath->GetFillRule() : GetFillRule(); + return GetOrBuildPath(drawTarget, fillRule); +} + +bool SVGGeometryElement::IsGeometryChangedViaCSS( + ComputedStyle const& aNewStyle, ComputedStyle const& aOldStyle) const { + nsAtom* name = NodeInfo()->NameAtom(); + if (name == nsGkAtoms::rect) { + return SVGRectElement::IsLengthChangedViaCSS(aNewStyle, aOldStyle); + } + if (name == nsGkAtoms::circle) { + return SVGCircleElement::IsLengthChangedViaCSS(aNewStyle, aOldStyle); + } + if (name == nsGkAtoms::ellipse) { + return SVGEllipseElement::IsLengthChangedViaCSS(aNewStyle, aOldStyle); + } + if (name == nsGkAtoms::path) { + return SVGPathElement::IsDPropertyChangedViaCSS(aNewStyle, aOldStyle); + } + return false; +} + +FillRule SVGGeometryElement::GetFillRule() { + FillRule fillRule = + FillRule::FILL_WINDING; // Equivalent to StyleFillRule::Nonzero + + bool res = SVGGeometryProperty::DoForComputedStyle( + this, [&](const ComputedStyle* s) { + const auto* styleSVG = s->StyleSVG(); + + MOZ_ASSERT(styleSVG->mFillRule == StyleFillRule::Nonzero || + styleSVG->mFillRule == StyleFillRule::Evenodd); + + if (styleSVG->mFillRule == StyleFillRule::Evenodd) { + fillRule = FillRule::FILL_EVEN_ODD; + } + }); + + if (!res) { + NS_WARNING("Couldn't get ComputedStyle for content in GetFillRule"); + } + + return fillRule; +} + +static Point GetPointFrom(const DOMPointInit& aPoint) { + return Point(aPoint.mX, aPoint.mY); +} + +bool SVGGeometryElement::IsPointInFill(const DOMPointInit& aPoint) { + // d is a presentation attribute, so make sure style is up to date: + FlushStyleIfNeeded(); + + RefPtr<Path> path = GetOrBuildPathForHitTest(); + if (!path) { + return false; + } + + auto point = GetPointFrom(aPoint); + return path->ContainsPoint(point, {}); +} + +bool SVGGeometryElement::IsPointInStroke(const DOMPointInit& aPoint) { + // stroke-* attributes and the d attribute are presentation attributes, so we + // flush the layout before building the path. + if (nsCOMPtr<Document> doc = GetComposedDoc()) { + doc->FlushPendingNotifications(FlushType::Layout); + } + + RefPtr<Path> path = GetOrBuildPathForHitTest(); + if (!path) { + return false; + } + + auto point = GetPointFrom(aPoint); + bool res = false; + SVGGeometryProperty::DoForComputedStyle(this, [&](const ComputedStyle* s) { + // Per spec, we should take vector-effect into account. + if (s->StyleSVGReset()->HasNonScalingStroke()) { + auto mat = SVGContentUtils::GetCTM(this, true); + if (mat.HasNonTranslation()) { + // We have non-scaling-stroke as well as a non-translation transform. + // We should transform the path first then apply the stroke on the + // transformed path to preserve the stroke-width. + RefPtr<PathBuilder> builder = path->TransformedCopyToBuilder(mat); + + path = builder->Finish(); + point = mat.TransformPoint(point); + } + } + + SVGContentUtils::AutoStrokeOptions strokeOptions; + SVGContentUtils::GetStrokeOptions(&strokeOptions, this, s, nullptr); + + res = path->StrokeContainsPoint(strokeOptions, point, {}); + }); + + return res; +} + +float SVGGeometryElement::GetTotalLengthForBinding() { + // d is a presentation attribute, so make sure style is up to date: + FlushStyleIfNeeded(); + return GetTotalLength(); +} + +already_AddRefed<DOMSVGPoint> SVGGeometryElement::GetPointAtLength( + float distance, ErrorResult& rv) { + // d is a presentation attribute, so make sure style is up to date: + FlushStyleIfNeeded(); + RefPtr<Path> path = GetOrBuildPathForMeasuring(); + if (!path) { + rv.ThrowInvalidStateError("No path available for measuring"); + return nullptr; + } + + return do_AddRef(new DOMSVGPoint(path->ComputePointAtLength( + clamped(distance, 0.f, path->ComputeLength())))); +} + +float SVGGeometryElement::GetPathLengthScale(PathLengthScaleForType aFor) { + MOZ_ASSERT(aFor == eForTextPath || aFor == eForStroking, "Unknown enum"); + if (mPathLength.IsExplicitlySet()) { + float authorsPathLengthEstimate = mPathLength.GetAnimValue(); + if (authorsPathLengthEstimate >= 0) { + RefPtr<Path> path = GetOrBuildPathForMeasuring(); + if (!path) { + // The path is empty or invalid so its length must be zero and + // we know that 0 / authorsPathLengthEstimate = 0. + return 0.0; + } + if (aFor == eForTextPath) { + // For textPath, a transform on the referenced path affects the + // textPath layout, so when calculating the actual path length + // we need to take that into account. + gfxMatrix matrix = PrependLocalTransformsTo(gfxMatrix()); + if (!matrix.IsIdentity()) { + RefPtr<PathBuilder> builder = + path->TransformedCopyToBuilder(ToMatrix(matrix)); + path = builder->Finish(); + } + } + return path->ComputeLength() / authorsPathLengthEstimate; + } + } + return 1.0; +} + +already_AddRefed<DOMSVGAnimatedNumber> SVGGeometryElement::PathLength() { + return mPathLength.ToDOMAnimatedNumber(this); +} + +float SVGGeometryElement::GetTotalLength() { + RefPtr<Path> flat = GetOrBuildPathForMeasuring(); + return flat ? flat->ComputeLength() : 0.f; +} + +void SVGGeometryElement::FlushStyleIfNeeded() { + // Note: we still can set d property on other elements which don't have d + // attribute, but we don't look at the d property on them, so here we only + // care about the element with d attribute, i.e. SVG path element. + if (GetPathDataAttrName() != nsGkAtoms::d) { + return; + } + + RefPtr<Document> doc = GetComposedDoc(); + if (!doc) { + return; + } + + doc->FlushPendingNotifications(FlushType::Style); +} + +} // namespace mozilla::dom |