SVGPathElement.cpp (13019B)
1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ 2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */ 3 /* This Source Code Form is subject to the terms of the Mozilla Public 4 * License, v. 2.0. If a copy of the MPL was not distributed with this 5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 6 7 #include "mozilla/dom/SVGPathElement.h" 8 9 #include <algorithm> 10 11 #include "SVGArcConverter.h" 12 #include "SVGGeometryProperty.h" 13 #include "SVGPathSegUtils.h" 14 #include "gfx2DGlue.h" 15 #include "gfxPlatform.h" 16 #include "mozAutoDocUpdate.h" 17 #include "mozilla/RefPtr.h" 18 #include "mozilla/SVGContentUtils.h" 19 #include "mozilla/dom/SVGPathElementBinding.h" 20 #include "mozilla/dom/SVGPathSegment.h" 21 #include "mozilla/gfx/2D.h" 22 #include "nsGkAtoms.h" 23 #include "nsIFrame.h" 24 #include "nsStyleConsts.h" 25 #include "nsStyleStruct.h" 26 #include "nsWindowSizes.h" 27 28 NS_IMPL_NS_NEW_SVG_ELEMENT(Path) 29 30 using namespace mozilla::gfx; 31 32 namespace mozilla::dom { 33 34 //---------------------------------------------------------------------- 35 // Helper class: AutoChangePathSegListNotifier 36 // Stack-based helper class to pair calls to WillChangePathSegList and 37 // DidChangePathSegList. 38 class MOZ_RAII AutoChangePathSegListNotifier : public mozAutoDocUpdate { 39 public: 40 explicit AutoChangePathSegListNotifier(SVGPathElement* aSVGPathElement) 41 : mozAutoDocUpdate(aSVGPathElement->GetComposedDoc(), true), 42 mSVGElement(aSVGPathElement) { 43 MOZ_ASSERT(mSVGElement, "Expecting non-null value"); 44 mSVGElement->WillChangePathSegList(*this); 45 } 46 47 ~AutoChangePathSegListNotifier() { 48 mSVGElement->DidChangePathSegList(*this); 49 if (mSVGElement->GetAnimPathSegList()->IsAnimating()) { 50 mSVGElement->AnimationNeedsResample(); 51 } 52 } 53 54 private: 55 SVGPathElement* const mSVGElement; 56 }; 57 58 JSObject* SVGPathElement::WrapNode(JSContext* aCx, 59 JS::Handle<JSObject*> aGivenProto) { 60 return SVGPathElement_Binding::Wrap(aCx, this, aGivenProto); 61 } 62 63 //---------------------------------------------------------------------- 64 // Implementation 65 66 SVGPathElement::SVGPathElement( 67 already_AddRefed<mozilla::dom::NodeInfo>&& aNodeInfo) 68 : SVGPathElementBase(std::move(aNodeInfo)) {} 69 70 //---------------------------------------------------------------------- 71 // memory reporting methods 72 73 void SVGPathElement::AddSizeOfExcludingThis(nsWindowSizes& aSizes, 74 size_t* aNodeSize) const { 75 SVGPathElementBase::AddSizeOfExcludingThis(aSizes, aNodeSize); 76 *aNodeSize += mD.SizeOfExcludingThis(aSizes.mState.mMallocSizeOf); 77 } 78 79 //---------------------------------------------------------------------- 80 // nsINode methods 81 82 NS_IMPL_ELEMENT_CLONE_WITH_INIT(SVGPathElement) 83 84 already_AddRefed<SVGPathSegment> SVGPathElement::GetPathSegmentAtLength( 85 float aDistance) { 86 FlushIfNeeded(); 87 RefPtr<SVGPathSegment> segment; 88 if (SVGGeometryProperty::DoForComputedStyle( 89 this, [&](const ComputedStyle* s) { 90 const auto& d = s->StyleSVGReset()->mD; 91 if (d.IsPath()) { 92 segment = SVGPathData::GetPathSegmentAtLength( 93 this, d.AsPath()._0.AsSpan(), aDistance); 94 } 95 })) { 96 return segment.forget(); 97 } 98 return SVGPathData::GetPathSegmentAtLength(this, mD.GetAnimValue().AsSpan(), 99 aDistance); 100 } 101 102 static void CreatePathSegments(SVGPathElement* aPathElement, 103 const StyleSVGPathData& aPathData, 104 nsTArray<RefPtr<SVGPathSegment>>& aValues, 105 bool aNormalize) { 106 if (aNormalize) { 107 StyleSVGPathData normalizedPathData; 108 Servo_SVGPathData_NormalizeAndReduce(&aPathData, &normalizedPathData); 109 Point pathStart(0.0, 0.0); 110 Point segStart(0.0, 0.0); 111 Point segEnd(0.0, 0.0); 112 for (const auto& cmd : normalizedPathData._0.AsSpan()) { 113 switch (cmd.tag) { 114 case StylePathCommand::Tag::Close: 115 segEnd = pathStart; 116 aValues.AppendElement(new SVGPathSegment(aPathElement, cmd)); 117 break; 118 case StylePathCommand::Tag::Move: 119 pathStart = segEnd = cmd.move.point.ToGfxPoint(); 120 aValues.AppendElement(new SVGPathSegment(aPathElement, cmd)); 121 break; 122 case StylePathCommand::Tag::Line: 123 segEnd = cmd.line.point.ToGfxPoint(); 124 aValues.AppendElement(new SVGPathSegment(aPathElement, cmd)); 125 break; 126 case StylePathCommand::Tag::CubicCurve: 127 segEnd = cmd.cubic_curve.point.ToGfxPoint(); 128 aValues.AppendElement(new SVGPathSegment(aPathElement, cmd)); 129 break; 130 case StylePathCommand::Tag::Arc: { 131 const auto& arc = cmd.arc; 132 segEnd = arc.point.ToGfxPoint(); 133 SVGArcConverter converter(segStart, arc.point.ToGfxPoint(), 134 arc.radii.ToGfxPoint(), arc.rotate, 135 arc.arc_size == StyleArcSize::Large, 136 arc.arc_sweep == StyleArcSweep::Cw); 137 Point cp1, cp2; 138 while (converter.GetNextSegment(&cp1, &cp2, &segEnd)) { 139 auto curve = StylePathCommand::CubicCurve( 140 StyleEndPoint<StyleCSSFloat>::ToPosition({segEnd.x, segEnd.y}), 141 StyleCurveControlPoint<StyleCSSFloat>::Absolute({cp1.x, cp1.y}), 142 StyleCurveControlPoint<StyleCSSFloat>::Absolute( 143 {cp2.x, cp2.y})); 144 aValues.AppendElement(new SVGPathSegment(aPathElement, curve)); 145 } 146 break; 147 } 148 default: 149 MOZ_ASSERT_UNREACHABLE("Unexpected path command"); 150 break; 151 } 152 segStart = segEnd; 153 } 154 return; 155 } 156 for (const auto& cmd : aPathData._0.AsSpan()) { 157 aValues.AppendElement(new SVGPathSegment(aPathElement, cmd)); 158 } 159 } 160 161 void SVGPathElement::GetPathData(const SVGPathDataSettings& aOptions, 162 nsTArray<RefPtr<SVGPathSegment>>& aValues) { 163 FlushIfNeeded(); 164 if (SVGGeometryProperty::DoForComputedStyle( 165 this, [&](const ComputedStyle* s) { 166 const auto& d = s->StyleSVGReset()->mD; 167 if (d.IsPath()) { 168 CreatePathSegments(this, d.AsPath(), aValues, 169 aOptions.mNormalize); 170 } 171 })) { 172 return; 173 } 174 CreatePathSegments(this, mD.GetAnimValue().RawData(), aValues, 175 aOptions.mNormalize); 176 } 177 178 void SVGPathElement::SetPathData(const Sequence<SVGPathSegmentInit>& aValues) { 179 AutoChangePathSegListNotifier notifier(this); 180 mD.SetBaseValueFromPathSegments(aValues); 181 } 182 183 //---------------------------------------------------------------------- 184 // SVGElement methods 185 186 /* virtual */ 187 bool SVGPathElement::HasValidDimensions() const { 188 bool hasPath = false; 189 auto callback = [&](const ComputedStyle* s) { 190 const nsStyleSVGReset* styleSVGReset = s->StyleSVGReset(); 191 hasPath = 192 styleSVGReset->mD.IsPath() && !styleSVGReset->mD.AsPath()._0.IsEmpty(); 193 }; 194 195 SVGGeometryProperty::DoForComputedStyle(this, callback); 196 // If hasPath is false, we may disable the pref of d property, so we fallback 197 // to check mD. 198 return hasPath || !mD.GetAnimValue().IsEmpty(); 199 } 200 201 //---------------------------------------------------------------------- 202 // nsIContent methods 203 204 NS_IMETHODIMP_(bool) 205 SVGPathElement::IsAttributeMapped(const nsAtom* name) const { 206 return name == nsGkAtoms::d || SVGPathElementBase::IsAttributeMapped(name); 207 } 208 209 already_AddRefed<Path> SVGPathElement::GetOrBuildPathForMeasuring() { 210 RefPtr<Path> path; 211 bool success = SVGGeometryProperty::DoForComputedStyle( 212 this, [&path](const ComputedStyle* s) { 213 const auto& d = s->StyleSVGReset()->mD; 214 if (d.IsNone()) { 215 return; 216 } 217 path = SVGPathData::BuildPathForMeasuring(d.AsPath()._0.AsSpan(), 218 s->EffectiveZoom().ToFloat()); 219 }); 220 return success ? path.forget() 221 : mD.GetAnimValue().BuildPathForMeasuring(1.0f); 222 } 223 224 //---------------------------------------------------------------------- 225 // SVGGeometryElement methods 226 227 bool SVGPathElement::AttributeDefinesGeometry(const nsAtom* aName) { 228 return aName == nsGkAtoms::d || aName == nsGkAtoms::pathLength; 229 } 230 231 bool SVGPathElement::IsMarkable() { return true; } 232 233 void SVGPathElement::GetMarkPoints(nsTArray<SVGMark>* aMarks) { 234 auto callback = [aMarks](const ComputedStyle* s) { 235 const nsStyleSVGReset* styleSVGReset = s->StyleSVGReset(); 236 if (styleSVGReset->mD.IsPath()) { 237 Span<const StylePathCommand> path = 238 styleSVGReset->mD.AsPath()._0.AsSpan(); 239 SVGPathData::GetMarkerPositioningData(path, s->EffectiveZoom().ToFloat(), 240 aMarks); 241 } 242 }; 243 244 if (SVGGeometryProperty::DoForComputedStyle(this, callback)) { 245 return; 246 } 247 248 mD.GetAnimValue().GetMarkerPositioningData(1.0f, aMarks); 249 } 250 251 void SVGPathElement::GetAsSimplePath(SimplePath* aSimplePath) { 252 aSimplePath->Reset(); 253 auto callback = [&](const ComputedStyle* s) { 254 const nsStyleSVGReset* styleSVGReset = s->StyleSVGReset(); 255 if (styleSVGReset->mD.IsPath()) { 256 auto pathData = styleSVGReset->mD.AsPath()._0.AsSpan(); 257 auto maybeRect = SVGPathToAxisAlignedRect(pathData); 258 if (maybeRect.isSome()) { 259 const Rect& r = *maybeRect; 260 float zoom = s->EffectiveZoom().ToFloat(); 261 aSimplePath->SetRect(r.x * zoom, r.y * zoom, r.width * zoom, 262 r.height * zoom); 263 } 264 } 265 }; 266 267 SVGGeometryProperty::DoForComputedStyle(this, callback); 268 } 269 270 already_AddRefed<Path> SVGPathElement::BuildPath(PathBuilder* aBuilder) { 271 // The Moz2D PathBuilder that our SVGPathData will be using only cares about 272 // the fill rule. However, in order to fulfill the requirements of the SVG 273 // spec regarding zero length sub-paths when square line caps are in use, 274 // SVGPathData needs to know our stroke-linecap style and, if "square", then 275 // also our stroke width. See the comment for 276 // ApproximateZeroLengthSubpathSquareCaps for more info. 277 278 auto strokeLineCap = StyleStrokeLinecap::Butt; 279 Float strokeWidth = 0; 280 RefPtr<Path> path; 281 282 auto callback = [&](const ComputedStyle* s) { 283 const nsStyleSVG* styleSVG = s->StyleSVG(); 284 // Note: the path that we return may be used for hit-testing, and SVG 285 // exposes hit-testing of strokes that are not actually painted. For that 286 // reason we do not check for eStyleSVGPaintType_None or check the stroke 287 // opacity here. 288 if (styleSVG->mStrokeLinecap != StyleStrokeLinecap::Butt) { 289 strokeLineCap = styleSVG->mStrokeLinecap; 290 strokeWidth = SVGContentUtils::GetStrokeWidth(this, s, nullptr); 291 } 292 293 const auto& d = s->StyleSVGReset()->mD; 294 if (d.IsPath()) { 295 path = SVGPathData::BuildPath(d.AsPath()._0.AsSpan(), aBuilder, 296 strokeLineCap, strokeWidth, {}, {}, 297 s->EffectiveZoom().ToFloat()); 298 } 299 }; 300 301 bool success = SVGGeometryProperty::DoForComputedStyle(this, callback); 302 if (success) { 303 return path.forget(); 304 } 305 306 // Fallback to use the d attribute if it exists. 307 return mD.GetAnimValue().BuildPath(aBuilder, strokeLineCap, strokeWidth, 308 1.0f); 309 } 310 311 bool SVGPathElement::GetDistancesFromOriginToEndsOfVisibleSegments( 312 FallibleTArray<double>* aOutput) { 313 bool ret = false; 314 auto callback = [&ret, aOutput](const ComputedStyle* s) { 315 const auto& d = s->StyleSVGReset()->mD; 316 ret = d.IsNone() || 317 SVGPathData::GetDistancesFromOriginToEndsOfVisibleSegments( 318 d.AsPath()._0.AsSpan(), aOutput); 319 }; 320 321 if (SVGGeometryProperty::DoForComputedStyle(this, callback)) { 322 return ret; 323 } 324 325 return mD.GetAnimValue().GetDistancesFromOriginToEndsOfVisibleSegments( 326 aOutput); 327 } 328 329 static bool PathIsClosed(Span<const StylePathCommand> aPath) { 330 return !aPath.IsEmpty() && aPath.rbegin()->IsClose(); 331 } 332 333 // Offset paths (including references to SVG Paths) are closed loops only if the 334 // final command in the path list is a closepath command ("z" or "Z"), otherwise 335 // they are unclosed intervals. 336 // https://drafts.fxtf.org/motion/#path-distance 337 bool SVGPathElement::IsClosedLoop() const { 338 bool isClosed = false; 339 340 auto callback = [&](const ComputedStyle* s) { 341 const nsStyleSVGReset* styleSVGReset = s->StyleSVGReset(); 342 if (styleSVGReset->mD.IsPath()) { 343 isClosed = PathIsClosed(styleSVGReset->mD.AsPath()._0.AsSpan()); 344 } 345 }; 346 347 if (SVGGeometryProperty::DoForComputedStyle(this, callback)) { 348 return isClosed; 349 } 350 351 return PathIsClosed(mD.GetAnimValue().AsSpan()); 352 } 353 354 /* static */ 355 bool SVGPathElement::IsDPropertyChangedViaCSS(const ComputedStyle& aNewStyle, 356 const ComputedStyle& aOldStyle) { 357 return aNewStyle.StyleSVGReset()->mD != aOldStyle.StyleSVGReset()->mD; 358 } 359 360 } // namespace mozilla::dom