/* -*- 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 "mozilla/dom/SVGPathElement.h" #include <algorithm> #include "DOMSVGPathSeg.h" #include "DOMSVGPathSegList.h" #include "DOMSVGPoint.h" #include "gfx2DGlue.h" #include "gfxPlatform.h" #include "mozilla/dom/SVGPathElementBinding.h" #include "mozilla/gfx/2D.h" #include "mozilla/RefPtr.h" #include "nsCOMPtr.h" #include "nsComputedDOMStyle.h" #include "nsGkAtoms.h" #include "nsStyleConsts.h" #include "nsStyleStruct.h" #include "SVGContentUtils.h" NS_IMPL_NS_NEW_NAMESPACED_SVG_ELEMENT(Path) using namespace mozilla::gfx; namespace mozilla { namespace dom { JSObject* SVGPathElement::WrapNode(JSContext *aCx, JS::Handle<JSObject*> aGivenProto) { return SVGPathElementBinding::Wrap(aCx, this, aGivenProto); } nsSVGElement::NumberInfo SVGPathElement::sNumberInfo = { &nsGkAtoms::pathLength, 0, false }; //---------------------------------------------------------------------- // Implementation SVGPathElement::SVGPathElement(already_AddRefed<mozilla::dom::NodeInfo>& aNodeInfo) : SVGPathElementBase(aNodeInfo) { } //---------------------------------------------------------------------- // memory reporting methods size_t SVGPathElement::SizeOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf) const { return SVGPathElementBase::SizeOfExcludingThis(aMallocSizeOf) + mD.SizeOfExcludingThis(aMallocSizeOf); } //---------------------------------------------------------------------- // nsIDOMNode methods NS_IMPL_ELEMENT_CLONE_WITH_INIT(SVGPathElement) already_AddRefed<SVGAnimatedNumber> SVGPathElement::PathLength() { return mPathLength.ToDOMAnimatedNumber(this); } float SVGPathElement::GetTotalLength() { RefPtr<Path> flat = GetOrBuildPathForMeasuring(); return flat ? flat->ComputeLength() : 0.f; } already_AddRefed<nsISVGPoint> SVGPathElement::GetPointAtLength(float distance, ErrorResult& rv) { RefPtr<Path> path = GetOrBuildPathForMeasuring(); if (!path) { rv.Throw(NS_ERROR_FAILURE); return nullptr; } float totalLength = path->ComputeLength(); if (mPathLength.IsExplicitlySet()) { float pathLength = mPathLength.GetAnimValue(); if (pathLength <= 0) { rv.Throw(NS_ERROR_FAILURE); return nullptr; } distance *= totalLength / pathLength; } distance = std::max(0.f, distance); distance = std::min(totalLength, distance); nsCOMPtr<nsISVGPoint> point = new DOMSVGPoint(path->ComputePointAtLength(distance)); return point.forget(); } uint32_t SVGPathElement::GetPathSegAtLength(float distance) { return mD.GetAnimValue().GetPathSegAtLength(distance); } already_AddRefed<DOMSVGPathSegClosePath> SVGPathElement::CreateSVGPathSegClosePath() { RefPtr<DOMSVGPathSegClosePath> pathSeg = new DOMSVGPathSegClosePath(); return pathSeg.forget(); } already_AddRefed<DOMSVGPathSegMovetoAbs> SVGPathElement::CreateSVGPathSegMovetoAbs(float x, float y) { RefPtr<DOMSVGPathSegMovetoAbs> pathSeg = new DOMSVGPathSegMovetoAbs(x, y); return pathSeg.forget(); } already_AddRefed<DOMSVGPathSegMovetoRel> SVGPathElement::CreateSVGPathSegMovetoRel(float x, float y) { RefPtr<DOMSVGPathSegMovetoRel> pathSeg = new DOMSVGPathSegMovetoRel(x, y); return pathSeg.forget(); } already_AddRefed<DOMSVGPathSegLinetoAbs> SVGPathElement::CreateSVGPathSegLinetoAbs(float x, float y) { RefPtr<DOMSVGPathSegLinetoAbs> pathSeg = new DOMSVGPathSegLinetoAbs(x, y); return pathSeg.forget(); } already_AddRefed<DOMSVGPathSegLinetoRel> SVGPathElement::CreateSVGPathSegLinetoRel(float x, float y) { RefPtr<DOMSVGPathSegLinetoRel> pathSeg = new DOMSVGPathSegLinetoRel(x, y); return pathSeg.forget(); } already_AddRefed<DOMSVGPathSegCurvetoCubicAbs> SVGPathElement::CreateSVGPathSegCurvetoCubicAbs(float x, float y, float x1, float y1, float x2, float y2) { // Note that we swap from DOM API argument order to the argument order used // in the <path> element's 'd' attribute (i.e. we put the arguments for the // end point of the segment last instead of first). RefPtr<DOMSVGPathSegCurvetoCubicAbs> pathSeg = new DOMSVGPathSegCurvetoCubicAbs(x1, y1, x2, y2, x, y); return pathSeg.forget(); } already_AddRefed<DOMSVGPathSegCurvetoCubicRel> SVGPathElement::CreateSVGPathSegCurvetoCubicRel(float x, float y, float x1, float y1, float x2, float y2) { // See comment in CreateSVGPathSegCurvetoCubicAbs RefPtr<DOMSVGPathSegCurvetoCubicRel> pathSeg = new DOMSVGPathSegCurvetoCubicRel(x1, y1, x2, y2, x, y); return pathSeg.forget(); } already_AddRefed<DOMSVGPathSegCurvetoQuadraticAbs> SVGPathElement::CreateSVGPathSegCurvetoQuadraticAbs(float x, float y, float x1, float y1) { // See comment in CreateSVGPathSegCurvetoCubicAbs RefPtr<DOMSVGPathSegCurvetoQuadraticAbs> pathSeg = new DOMSVGPathSegCurvetoQuadraticAbs(x1, y1, x, y); return pathSeg.forget(); } already_AddRefed<DOMSVGPathSegCurvetoQuadraticRel> SVGPathElement::CreateSVGPathSegCurvetoQuadraticRel(float x, float y, float x1, float y1) { // See comment in CreateSVGPathSegCurvetoCubicAbs RefPtr<DOMSVGPathSegCurvetoQuadraticRel> pathSeg = new DOMSVGPathSegCurvetoQuadraticRel(x1, y1, x, y); return pathSeg.forget(); } already_AddRefed<DOMSVGPathSegArcAbs> SVGPathElement::CreateSVGPathSegArcAbs(float x, float y, float r1, float r2, float angle, bool largeArcFlag, bool sweepFlag) { // See comment in CreateSVGPathSegCurvetoCubicAbs RefPtr<DOMSVGPathSegArcAbs> pathSeg = new DOMSVGPathSegArcAbs(r1, r2, angle, largeArcFlag, sweepFlag, x, y); return pathSeg.forget(); } already_AddRefed<DOMSVGPathSegArcRel> SVGPathElement::CreateSVGPathSegArcRel(float x, float y, float r1, float r2, float angle, bool largeArcFlag, bool sweepFlag) { // See comment in CreateSVGPathSegCurvetoCubicAbs RefPtr<DOMSVGPathSegArcRel> pathSeg = new DOMSVGPathSegArcRel(r1, r2, angle, largeArcFlag, sweepFlag, x, y); return pathSeg.forget(); } already_AddRefed<DOMSVGPathSegLinetoHorizontalAbs> SVGPathElement::CreateSVGPathSegLinetoHorizontalAbs(float x) { RefPtr<DOMSVGPathSegLinetoHorizontalAbs> pathSeg = new DOMSVGPathSegLinetoHorizontalAbs(x); return pathSeg.forget(); } already_AddRefed<DOMSVGPathSegLinetoHorizontalRel> SVGPathElement::CreateSVGPathSegLinetoHorizontalRel(float x) { RefPtr<DOMSVGPathSegLinetoHorizontalRel> pathSeg = new DOMSVGPathSegLinetoHorizontalRel(x); return pathSeg.forget(); } already_AddRefed<DOMSVGPathSegLinetoVerticalAbs> SVGPathElement::CreateSVGPathSegLinetoVerticalAbs(float y) { RefPtr<DOMSVGPathSegLinetoVerticalAbs> pathSeg = new DOMSVGPathSegLinetoVerticalAbs(y); return pathSeg.forget(); } already_AddRefed<DOMSVGPathSegLinetoVerticalRel> SVGPathElement::CreateSVGPathSegLinetoVerticalRel(float y) { RefPtr<DOMSVGPathSegLinetoVerticalRel> pathSeg = new DOMSVGPathSegLinetoVerticalRel(y); return pathSeg.forget(); } already_AddRefed<DOMSVGPathSegCurvetoCubicSmoothAbs> SVGPathElement::CreateSVGPathSegCurvetoCubicSmoothAbs(float x, float y, float x2, float y2) { // See comment in CreateSVGPathSegCurvetoCubicAbs RefPtr<DOMSVGPathSegCurvetoCubicSmoothAbs> pathSeg = new DOMSVGPathSegCurvetoCubicSmoothAbs(x2, y2, x, y); return pathSeg.forget(); } already_AddRefed<DOMSVGPathSegCurvetoCubicSmoothRel> SVGPathElement::CreateSVGPathSegCurvetoCubicSmoothRel(float x, float y, float x2, float y2) { // See comment in CreateSVGPathSegCurvetoCubicAbs RefPtr<DOMSVGPathSegCurvetoCubicSmoothRel> pathSeg = new DOMSVGPathSegCurvetoCubicSmoothRel(x2, y2, x, y); return pathSeg.forget(); } already_AddRefed<DOMSVGPathSegCurvetoQuadraticSmoothAbs> SVGPathElement::CreateSVGPathSegCurvetoQuadraticSmoothAbs(float x, float y) { RefPtr<DOMSVGPathSegCurvetoQuadraticSmoothAbs> pathSeg = new DOMSVGPathSegCurvetoQuadraticSmoothAbs(x, y); return pathSeg.forget(); } already_AddRefed<DOMSVGPathSegCurvetoQuadraticSmoothRel> SVGPathElement::CreateSVGPathSegCurvetoQuadraticSmoothRel(float x, float y) { RefPtr<DOMSVGPathSegCurvetoQuadraticSmoothRel> pathSeg = new DOMSVGPathSegCurvetoQuadraticSmoothRel(x, y); return pathSeg.forget(); } already_AddRefed<DOMSVGPathSegList> SVGPathElement::PathSegList() { return DOMSVGPathSegList::GetDOMWrapper(mD.GetBaseValKey(), this, false); } already_AddRefed<DOMSVGPathSegList> SVGPathElement::AnimatedPathSegList() { return DOMSVGPathSegList::GetDOMWrapper(mD.GetAnimValKey(), this, true); } //---------------------------------------------------------------------- // nsSVGElement methods /* virtual */ bool SVGPathElement::HasValidDimensions() const { return !mD.GetAnimValue().IsEmpty(); } nsSVGElement::NumberAttributesInfo SVGPathElement::GetNumberInfo() { return NumberAttributesInfo(&mPathLength, &sNumberInfo, 1); } //---------------------------------------------------------------------- // nsIContent methods NS_IMETHODIMP_(bool) SVGPathElement::IsAttributeMapped(const nsIAtom* name) const { static const MappedAttributeEntry* const map[] = { sMarkersMap }; return FindAttributeDependence(name, map) || SVGPathElementBase::IsAttributeMapped(name); } already_AddRefed<Path> SVGPathElement::GetOrBuildPathForMeasuring() { return mD.GetAnimValue().BuildPathForMeasuring(); } //---------------------------------------------------------------------- // nsSVGPathGeometryElement methods bool SVGPathElement::AttributeDefinesGeometry(const nsIAtom *aName) { return aName == nsGkAtoms::d || aName == nsGkAtoms::pathLength; } bool SVGPathElement::IsMarkable() { return true; } void SVGPathElement::GetMarkPoints(nsTArray<nsSVGMark> *aMarks) { mD.GetAnimValue().GetMarkerPositioningData(aMarks); } float SVGPathElement::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<Path> SVGPathElement::BuildPath(PathBuilder* aBuilder) { // The Moz2D PathBuilder that our SVGPathData will be using only cares about // the fill rule. However, in order to fulfill the requirements of the SVG // spec regarding zero length sub-paths when square line caps are in use, // SVGPathData needs to know our stroke-linecap style and, if "square", then // also our stroke width. See the comment for // ApproximateZeroLengthSubpathSquareCaps for more info. uint8_t strokeLineCap = NS_STYLE_STROKE_LINECAP_BUTT; Float strokeWidth = 0; RefPtr<nsStyleContext> styleContext = nsComputedDOMStyle::GetStyleContextForElementNoFlush(this, nullptr, nullptr); if (styleContext) { const nsStyleSVG* style = styleContext->StyleSVG(); // Note: the path that we return may be used for hit-testing, and SVG // exposes hit-testing of strokes that are not actually painted. For that // reason we do not check for eStyleSVGPaintType_None or check the stroke // opacity here. if (style->mStrokeLinecap != NS_STYLE_STROKE_LINECAP_BUTT) { strokeLineCap = style->mStrokeLinecap; strokeWidth = SVGContentUtils::GetStrokeWidth(this, styleContext, nullptr); } } return mD.GetAnimValue().BuildPath(aBuilder, strokeLineCap, strokeWidth); } } // namespace dom } // namespace mozilla