SVGPathData.cpp (28796B)
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 "SVGPathData.h" 8 9 #include "SVGArcConverter.h" 10 #include "SVGContentUtils.h" 11 #include "SVGGeometryElement.h" 12 #include "SVGPathSegUtils.h" 13 #include "gfx2DGlue.h" 14 #include "gfxPlatform.h" 15 #include "mozilla/RefPtr.h" 16 #include "mozilla/dom/SVGPathSegment.h" 17 #include "mozilla/gfx/2D.h" 18 #include "mozilla/gfx/Point.h" 19 #include "mozilla/gfx/Types.h" 20 #include "nsError.h" 21 #include "nsString.h" 22 #include "nsStyleConsts.h" 23 24 using namespace mozilla::gfx; 25 26 namespace mozilla { 27 28 nsresult SVGPathData::SetValueFromString(const nsACString& aValue) { 29 // We don't use a temp variable since the spec says to parse everything up to 30 // the first error. We still return any error though so that callers know if 31 // there's a problem. 32 bool ok = Servo_SVGPathData_Parse(&aValue, &mData); 33 return ok ? NS_OK : NS_ERROR_DOM_SYNTAX_ERR; 34 } 35 36 void SVGPathData::GetValueAsString(nsACString& aValue) const { 37 Servo_SVGPathData_ToString(&mData, &aValue); 38 } 39 40 bool SVGPathData::GetDistancesFromOriginToEndsOfVisibleSegments( 41 FallibleTArray<double>* aOutput) const { 42 return GetDistancesFromOriginToEndsOfVisibleSegments(AsSpan(), aOutput); 43 } 44 45 /* static */ 46 bool SVGPathData::GetDistancesFromOriginToEndsOfVisibleSegments( 47 Span<const StylePathCommand> aPath, FallibleTArray<double>* aOutput) { 48 SVGPathTraversalState state; 49 50 aOutput->Clear(); 51 52 bool firstMoveToIsChecked = false; 53 for (const auto& cmd : aPath) { 54 SVGPathSegUtils::TraversePathSegment(cmd, state); 55 if (!std::isfinite(state.length)) { 56 return false; 57 } 58 59 // We skip all moveto commands except for the initial moveto. 60 if (!cmd.IsMove() || !firstMoveToIsChecked) { 61 if (!aOutput->AppendElement(state.length, fallible)) { 62 return false; 63 } 64 } 65 66 if (cmd.IsMove() && !firstMoveToIsChecked) { 67 firstMoveToIsChecked = true; 68 } 69 } 70 71 return true; 72 } 73 74 /* static */ 75 already_AddRefed<dom::SVGPathSegment> SVGPathData::GetPathSegmentAtLength( 76 dom::SVGPathElement* aPathElement, Span<const StylePathCommand> aPath, 77 float aDistance) { 78 SVGPathTraversalState state; 79 80 for (const auto& cmd : aPath) { 81 SVGPathSegUtils::TraversePathSegment(cmd, state); 82 if (state.length >= aDistance) { 83 return do_AddRef(new dom::SVGPathSegment(aPathElement, cmd)); 84 } 85 } 86 return nullptr; 87 } 88 89 /** 90 * The SVG spec says we have to paint stroke caps for zero length subpaths: 91 * 92 * http://www.w3.org/TR/SVG11/implnote.html#PathElementImplementationNotes 93 * 94 * Cairo only does this for |stroke-linecap: round| and not for 95 * |stroke-linecap: square| (since that's what Adobe Acrobat has always done). 96 * Most likely the other backends that DrawTarget uses have the same behavior. 97 * 98 * To help us conform to the SVG spec we have this helper function to draw an 99 * approximation of square caps for zero length subpaths. It does this by 100 * inserting a subpath containing a single user space axis aligned straight 101 * line that is as small as it can be while minimizing the risk of it being 102 * thrown away by the DrawTarget's backend for being too small to affect 103 * rendering. The idea is that we'll then get stroke caps drawn for this axis 104 * aligned line, creating an axis aligned rectangle that approximates the 105 * square that would ideally be drawn. 106 * 107 * Since we don't have any information about transforms from user space to 108 * device space, we choose the length of the small line that we insert by 109 * making it a small percentage of the stroke width of the path. This should 110 * hopefully allow us to make the line as long as possible (to avoid rounding 111 * issues in the backend resulting in the backend seeing it as having zero 112 * length) while still avoiding the small rectangle being noticeably different 113 * from a square. 114 * 115 * Note that this function inserts a subpath into the current gfx path that 116 * will be present during both fill and stroke operations. 117 */ 118 static void ApproximateZeroLengthSubpathSquareCaps(PathBuilder* aPB, 119 const Point& aPoint, 120 Float aStrokeWidth) { 121 // Note that caps are proportional to stroke width, so if stroke width is 122 // zero it's actually fine for |tinyLength| below to end up being zero. 123 // However, it would be a waste to inserting a LineTo in that case, so better 124 // not to. 125 MOZ_ASSERT(aStrokeWidth > 0.0f, 126 "Make the caller check for this, or check it here"); 127 128 // The fraction of the stroke width that we choose for the length of the 129 // line is rather arbitrary, other than being chosen to meet the requirements 130 // described in the comment above. 131 132 Float tinyLength = aStrokeWidth / SVG_ZERO_LENGTH_PATH_FIX_FACTOR; 133 134 aPB->LineTo(aPoint + Point(tinyLength, 0)); 135 aPB->MoveTo(aPoint); 136 } 137 138 #define MAYBE_APPROXIMATE_ZERO_LENGTH_SUBPATH_SQUARE_CAPS_TO_DT \ 139 do { \ 140 if (!subpathHasLength && hasLineCaps && aStrokeWidth > 0 && \ 141 subpathContainsNonMoveTo && IsValidType(prevSegType) && \ 142 (!IsMoveto(prevSegType) || IsClosePath(segType))) { \ 143 ApproximateZeroLengthSubpathSquareCaps(aBuilder, segStart, \ 144 aStrokeWidth); \ 145 } \ 146 } while (0) 147 148 already_AddRefed<Path> SVGPathData::BuildPath(PathBuilder* aBuilder, 149 StyleStrokeLinecap aStrokeLineCap, 150 Float aStrokeWidth, 151 float aZoom) const { 152 return BuildPath(AsSpan(), aBuilder, aStrokeLineCap, aStrokeWidth, {}, {}, 153 aZoom); 154 } 155 156 #undef MAYBE_APPROXIMATE_ZERO_LENGTH_SUBPATH_SQUARE_CAPS_TO_DT 157 158 already_AddRefed<Path> SVGPathData::BuildPathForMeasuring(float aZoom) const { 159 // Since the path that we return will not be used for painting it doesn't 160 // matter what we pass to CreatePathBuilder as aFillRule. Hawever, we do want 161 // to pass something other than NS_STYLE_STROKE_LINECAP_SQUARE as 162 // aStrokeLineCap to avoid the insertion of extra little lines (by 163 // ApproximateZeroLengthSubpathSquareCaps), in which case the value that we 164 // pass as aStrokeWidth doesn't matter (since it's only used to determine the 165 // length of those extra little lines). 166 167 RefPtr<DrawTarget> drawTarget = 168 gfxPlatform::GetPlatform()->ScreenReferenceDrawTarget(); 169 RefPtr<PathBuilder> builder = 170 drawTarget->CreatePathBuilder(FillRule::FILL_WINDING); 171 return BuildPath(builder, StyleStrokeLinecap::Butt, 0, aZoom); 172 } 173 174 /* static */ 175 already_AddRefed<Path> SVGPathData::BuildPathForMeasuring( 176 Span<const StylePathCommand> aPath, float aZoom) { 177 RefPtr<DrawTarget> drawTarget = 178 gfxPlatform::GetPlatform()->ScreenReferenceDrawTarget(); 179 RefPtr<PathBuilder> builder = 180 drawTarget->CreatePathBuilder(FillRule::FILL_WINDING); 181 return BuildPath(aPath, builder, StyleStrokeLinecap::Butt, 0, {}, {}, aZoom); 182 } 183 184 static inline StyleCSSFloat GetRotate(const StyleCSSFloat& aAngle) { 185 return aAngle; 186 } 187 188 static inline StyleCSSFloat GetRotate(const StyleAngle& aAngle) { 189 return aAngle.ToDegrees(); 190 } 191 192 template <typename Angle, typename Position, typename LP> 193 static already_AddRefed<Path> BuildPathInternal( 194 Span<const StyleGenericShapeCommand<Angle, Position, LP>> aPath, 195 PathBuilder* aBuilder, StyleStrokeLinecap aStrokeLineCap, 196 Float aStrokeWidth, const CSSSize& aPercentageBasis, const Point& aOffset, 197 float aZoomFactor) { 198 using Command = StyleGenericShapeCommand<Angle, Position, LP>; 199 200 if (aPath.IsEmpty() || !aPath[0].IsMove()) { 201 return nullptr; // paths without an initial moveto are invalid 202 } 203 204 bool hasLineCaps = aStrokeLineCap != StyleStrokeLinecap::Butt; 205 bool subpathHasLength = false; // visual length 206 bool subpathContainsNonMoveTo = false; 207 208 const Command* seg = nullptr; 209 const Command* prevSeg = nullptr; 210 Point pathStart(0.0, 0.0); // start point of [sub]path 211 Point segStart(0.0, 0.0); 212 Point segEnd; 213 Point cp1, cp2; // previous bezier's control points 214 Point tcp1, tcp2; // temporaries 215 216 auto maybeApproximateZeroLengthSubpathSquareCaps = 217 [&](const Command* aPrevSeg, const Command* aSeg) { 218 if (!subpathHasLength && hasLineCaps && aStrokeWidth > 0 && 219 subpathContainsNonMoveTo && aPrevSeg && aSeg && 220 (!aPrevSeg->IsMove() || aSeg->IsClose())) { 221 ApproximateZeroLengthSubpathSquareCaps(aBuilder, segStart, 222 aStrokeWidth); 223 } 224 }; 225 226 auto scale = [aOffset, aZoomFactor](const Point& p) { 227 return Point(p.x * aZoomFactor, p.y * aZoomFactor) + aOffset; 228 }; 229 230 // Regarding cp1 and cp2: If the previous segment was a cubic bezier curve, 231 // then cp2 is its second control point. If the previous segment was a 232 // quadratic curve, then cp1 is its (only) control point. 233 234 for (const auto& cmd : aPath) { 235 seg = &cmd; 236 switch (cmd.tag) { 237 case Command::Tag::Close: 238 // set this early to allow drawing of square caps for "M{x},{y} Z": 239 subpathContainsNonMoveTo = true; 240 maybeApproximateZeroLengthSubpathSquareCaps(prevSeg, seg); 241 segEnd = pathStart; 242 aBuilder->Close(); 243 break; 244 case Command::Tag::Move: { 245 maybeApproximateZeroLengthSubpathSquareCaps(prevSeg, seg); 246 const Point& p = cmd.move.point.ToGfxPoint(aPercentageBasis); 247 pathStart = segEnd = cmd.move.point.IsToPosition() ? p : segStart + p; 248 aBuilder->MoveTo(scale(segEnd)); 249 subpathHasLength = false; 250 break; 251 } 252 case Command::Tag::Line: { 253 const Point& p = cmd.line.point.ToGfxPoint(aPercentageBasis); 254 segEnd = cmd.line.point.IsToPosition() ? p : segStart + p; 255 if (segEnd != segStart) { 256 subpathHasLength = true; 257 aBuilder->LineTo(scale(segEnd)); 258 } 259 break; 260 } 261 case Command::Tag::CubicCurve: 262 segEnd = cmd.cubic_curve.point.ToGfxPoint(aPercentageBasis); 263 segEnd = 264 cmd.cubic_curve.point.IsByCoordinate() ? segEnd + segStart : segEnd; 265 cp1 = cmd.cubic_curve.control1.ToGfxPoint(segStart, segEnd, 266 aPercentageBasis); 267 cp2 = cmd.cubic_curve.control2.ToGfxPoint(segStart, segEnd, 268 aPercentageBasis); 269 270 if (segEnd != segStart || segEnd != cp1 || segEnd != cp2) { 271 subpathHasLength = true; 272 aBuilder->BezierTo(scale(cp1), scale(cp2), scale(segEnd)); 273 } 274 break; 275 276 case Command::Tag::QuadCurve: 277 segEnd = cmd.quad_curve.point.ToGfxPoint(aPercentageBasis); 278 segEnd = cmd.quad_curve.point.IsByCoordinate() 279 ? segEnd + segStart 280 : segEnd; // set before setting tcp2! 281 cp1 = cmd.quad_curve.control1.ToGfxPoint(segStart, segEnd, 282 aPercentageBasis); 283 284 // Convert quadratic curve to cubic curve: 285 tcp1 = segStart + (cp1 - segStart) * 2 / 3; 286 tcp2 = cp1 + (segEnd - cp1) / 3; 287 288 if (segEnd != segStart || segEnd != cp1) { 289 subpathHasLength = true; 290 aBuilder->BezierTo(scale(tcp1), scale(tcp2), scale(segEnd)); 291 } 292 break; 293 294 case Command::Tag::Arc: { 295 const auto& arc = cmd.arc; 296 const Point& radii = arc.radii.ToGfxPoint(aPercentageBasis); 297 segEnd = arc.point.ToGfxPoint(aPercentageBasis); 298 if (arc.point.IsByCoordinate()) { 299 segEnd += segStart; 300 } 301 if (segEnd != segStart) { 302 subpathHasLength = true; 303 if (radii.x == 0.0f || radii.y == 0.0f) { 304 aBuilder->LineTo(scale(segEnd)); 305 } else { 306 const bool arc_is_large = arc.arc_size == StyleArcSize::Large; 307 const bool arc_is_cw = arc.arc_sweep == StyleArcSweep::Cw; 308 SVGArcConverter converter(segStart, segEnd, radii, 309 GetRotate(arc.rotate), arc_is_large, 310 arc_is_cw); 311 while (converter.GetNextSegment(&cp1, &cp2, &segEnd)) { 312 aBuilder->BezierTo(scale(cp1), scale(cp2), scale(segEnd)); 313 } 314 } 315 } 316 break; 317 } 318 case Command::Tag::HLine: { 319 const auto x = cmd.h_line.x.ToGfxCoord(aPercentageBasis.width); 320 if (cmd.h_line.x.IsToPosition()) { 321 segEnd = Point(x, segStart.y); 322 } else { 323 segEnd = segStart + Point(x, 0.0f); 324 } 325 326 if (segEnd != segStart) { 327 subpathHasLength = true; 328 aBuilder->LineTo(scale(segEnd)); 329 } 330 break; 331 } 332 case Command::Tag::VLine: { 333 const auto y = cmd.v_line.y.ToGfxCoord(aPercentageBasis.height); 334 if (cmd.v_line.y.IsToPosition()) { 335 segEnd = Point(segStart.x, y); 336 } else { 337 segEnd = segStart + Point(0.0f, y); 338 } 339 340 if (segEnd != segStart) { 341 subpathHasLength = true; 342 aBuilder->LineTo(scale(segEnd)); 343 } 344 break; 345 } 346 case Command::Tag::SmoothCubic: 347 segEnd = cmd.smooth_cubic.point.ToGfxPoint(aPercentageBasis); 348 segEnd = cmd.smooth_cubic.point.IsByCoordinate() ? segEnd + segStart 349 : segEnd; 350 cp1 = prevSeg && prevSeg->IsCubicType() ? segStart * 2 - cp2 : segStart; 351 cp2 = cmd.smooth_cubic.control2.ToGfxPoint(segStart, segEnd, 352 aPercentageBasis); 353 354 if (segEnd != segStart || segEnd != cp1 || segEnd != cp2) { 355 subpathHasLength = true; 356 aBuilder->BezierTo(scale(cp1), scale(cp2), scale(segEnd)); 357 } 358 break; 359 360 case Command::Tag::SmoothQuad: { 361 cp1 = prevSeg && prevSeg->IsQuadraticType() ? segStart * 2 - cp1 362 : segStart; 363 // Convert quadratic curve to cubic curve: 364 tcp1 = segStart + (cp1 - segStart) * 2 / 3; 365 366 const Point& p = cmd.smooth_quad.point.ToGfxPoint(aPercentageBasis); 367 // set before setting tcp2! 368 segEnd = cmd.smooth_quad.point.IsToPosition() ? p : segStart + p; 369 tcp2 = cp1 + (segEnd - cp1) / 3; 370 371 if (segEnd != segStart || segEnd != cp1) { 372 subpathHasLength = true; 373 aBuilder->BezierTo(scale(tcp1), scale(tcp2), scale(segEnd)); 374 } 375 break; 376 } 377 } 378 379 subpathContainsNonMoveTo = !cmd.IsMove(); 380 prevSeg = seg; 381 segStart = segEnd; 382 } 383 384 MOZ_ASSERT(prevSeg == seg, "prevSegType should be left at the final segType"); 385 386 maybeApproximateZeroLengthSubpathSquareCaps(prevSeg, seg); 387 388 return aBuilder->Finish(); 389 } 390 391 /* static */ 392 already_AddRefed<Path> SVGPathData::BuildPath( 393 Span<const StylePathCommand> aPath, PathBuilder* aBuilder, 394 StyleStrokeLinecap aStrokeLineCap, Float aStrokeWidth, 395 const CSSSize& aBasis, const gfx::Point& aOffset, float aZoomFactor) { 396 return BuildPathInternal(aPath, aBuilder, aStrokeLineCap, aStrokeWidth, 397 aBasis, aOffset, aZoomFactor); 398 } 399 400 /* static */ 401 already_AddRefed<Path> SVGPathData::BuildPath( 402 Span<const StyleShapeCommand> aShape, PathBuilder* aBuilder, 403 StyleStrokeLinecap aStrokeLineCap, Float aStrokeWidth, 404 const CSSSize& aBasis, const gfx::Point& aOffset, float aZoomFactor) { 405 return BuildPathInternal(aShape, aBuilder, aStrokeLineCap, aStrokeWidth, 406 aBasis, aOffset, aZoomFactor); 407 } 408 409 static double AngleOfVector(const Point& aVector) { 410 // C99 says about atan2 "A domain error may occur if both arguments are 411 // zero" and "On a domain error, the function returns an implementation- 412 // defined value". In the case of atan2 the implementation-defined value 413 // seems to commonly be zero, but it could just as easily be a NaN value. 414 // We specifically want zero in this case, hence the check: 415 416 return (aVector != Point(0.0, 0.0)) ? atan2(aVector.y, aVector.x) : 0.0; 417 } 418 419 static float AngleOfVector(const Point& cp1, const Point& cp2) { 420 return static_cast<float>(AngleOfVector(cp1 - cp2)); 421 } 422 423 // This implements F.6.5 and F.6.6 of 424 // http://www.w3.org/TR/SVG11/implnote.html#ArcImplementationNotes 425 static std::tuple<float, float, float, float> 426 /* rx, ry, segStartAngle, segEndAngle */ 427 ComputeSegAnglesAndCorrectRadii(const Point& aSegStart, const Point& aSegEnd, 428 const float aAngle, const bool aLargeArcFlag, 429 const bool aSweepFlag, const float aRx, 430 const float aRy) { 431 float rx = fabs(aRx); // F.6.6.1 432 float ry = fabs(aRy); 433 434 // F.6.5.1: 435 const float angle = static_cast<float>(aAngle * M_PI / 180.0); 436 double x1p = cos(angle) * (aSegStart.x - aSegEnd.x) / 2.0 + 437 sin(angle) * (aSegStart.y - aSegEnd.y) / 2.0; 438 double y1p = -sin(angle) * (aSegStart.x - aSegEnd.x) / 2.0 + 439 cos(angle) * (aSegStart.y - aSegEnd.y) / 2.0; 440 441 // This is the root in F.6.5.2 and the numerator under that root: 442 double root; 443 double numerator = 444 rx * rx * ry * ry - rx * rx * y1p * y1p - ry * ry * x1p * x1p; 445 446 if (numerator >= 0.0) { 447 root = sqrt(numerator / (rx * rx * y1p * y1p + ry * ry * x1p * x1p)); 448 if (aLargeArcFlag == aSweepFlag) root = -root; 449 } else { 450 // F.6.6 step 3 - |numerator < 0.0|. This is equivalent to the result 451 // of F.6.6.2 (lamedh) being greater than one. What we have here is 452 // ellipse radii that are too small for the ellipse to reach between 453 // segStart and segEnd. We scale the radii up uniformly so that the 454 // ellipse is just big enough to fit (i.e. to the point where there is 455 // exactly one solution). 456 457 double lamedh = 458 1.0 - numerator / (rx * rx * ry * ry); // equiv to eqn F.6.6.2 459 double s = sqrt(lamedh); 460 rx = static_cast<float>((double)rx * s); // F.6.6.3 461 ry = static_cast<float>((double)ry * s); 462 root = 0.0; 463 } 464 465 double cxp = root * rx * y1p / ry; // F.6.5.2 466 double cyp = -root * ry * x1p / rx; 467 468 double theta = 469 AngleOfVector(Point(static_cast<float>((x1p - cxp) / rx), 470 static_cast<float>((y1p - cyp) / ry))); // F.6.5.5 471 double delta = 472 AngleOfVector(Point(static_cast<float>((-x1p - cxp) / rx), 473 static_cast<float>((-y1p - cyp) / ry))) - // F.6.5.6 474 theta; 475 if (!aSweepFlag && delta > 0) { 476 delta -= 2.0 * M_PI; 477 } else if (aSweepFlag && delta < 0) { 478 delta += 2.0 * M_PI; 479 } 480 481 double tx1, ty1, tx2, ty2; 482 tx1 = -cos(angle) * rx * sin(theta) - sin(angle) * ry * cos(theta); 483 ty1 = -sin(angle) * rx * sin(theta) + cos(angle) * ry * cos(theta); 484 tx2 = -cos(angle) * rx * sin(theta + delta) - 485 sin(angle) * ry * cos(theta + delta); 486 ty2 = -sin(angle) * rx * sin(theta + delta) + 487 cos(angle) * ry * cos(theta + delta); 488 489 if (delta < 0.0f) { 490 tx1 = -tx1; 491 ty1 = -ty1; 492 tx2 = -tx2; 493 ty2 = -ty2; 494 } 495 496 return {rx, ry, static_cast<float>(atan2(ty1, tx1)), 497 static_cast<float>(atan2(ty2, tx2))}; 498 } 499 500 void SVGPathData::GetMarkerPositioningData(float aZoom, 501 nsTArray<SVGMark>* aMarks) const { 502 return GetMarkerPositioningData(AsSpan(), aZoom, aMarks); 503 } 504 505 // Basically, this is identical to the above function, but replace |mData| with 506 // |aPath|. We probably can factor out some identical calculation, but I believe 507 // the above one will be removed because we will use any kind of array of 508 // StylePathCommand for SVG d attribute in the future. 509 /* static */ 510 void SVGPathData::GetMarkerPositioningData(Span<const StylePathCommand> aPath, 511 float aZoom, 512 nsTArray<SVGMark>* aMarks) { 513 if (aPath.IsEmpty()) { 514 return; 515 } 516 517 // info on current [sub]path (reset every M command): 518 Point pathStart(0.0, 0.0); 519 float pathStartAngle = 0.0f; 520 uint32_t pathStartIndex = 0; 521 522 // info on previous segment: 523 const StylePathCommand* prevSeg = nullptr; 524 Point prevSegEnd(0.0, 0.0); 525 float prevSegEndAngle = 0.0f; 526 Point prevCP; // if prev seg was a bezier, this was its last control point 527 528 for (const StylePathCommand& cmd : aPath) { 529 Point& segStart = prevSegEnd; 530 Point segEnd; 531 float segStartAngle, segEndAngle; 532 533 switch (cmd.tag) // to find segStartAngle, segEnd and segEndAngle 534 { 535 case StylePathCommand::Tag::Close: 536 segEnd = pathStart; 537 segStartAngle = segEndAngle = AngleOfVector(segEnd, segStart); 538 break; 539 540 case StylePathCommand::Tag::Move: { 541 const Point& p = cmd.move.point.ToGfxPoint() * aZoom; 542 pathStart = segEnd = cmd.move.point.IsToPosition() ? p : segStart + p; 543 pathStartIndex = aMarks->Length(); 544 // If authors are going to specify multiple consecutive moveto commands 545 // with markers, me might as well make the angle do something useful: 546 segStartAngle = segEndAngle = AngleOfVector(segEnd, segStart); 547 break; 548 } 549 case StylePathCommand::Tag::Line: { 550 const Point& p = cmd.line.point.ToGfxPoint() * aZoom; 551 segEnd = cmd.line.point.IsToPosition() ? p : segStart + p; 552 segStartAngle = segEndAngle = AngleOfVector(segEnd, segStart); 553 break; 554 } 555 case StylePathCommand::Tag::CubicCurve: { 556 segEnd = cmd.cubic_curve.point.ToGfxPoint() * aZoom; 557 segEnd = 558 cmd.cubic_curve.point.IsByCoordinate() ? segEnd + segStart : segEnd; 559 Point cp1 = 560 cmd.cubic_curve.control1.ToGfxPoint(segStart, segEnd) * aZoom; 561 Point cp2 = 562 cmd.cubic_curve.control2.ToGfxPoint(segStart, segEnd) * aZoom; 563 564 prevCP = cp2; 565 segStartAngle = AngleOfVector( 566 cp1 == segStart ? (cp1 == cp2 ? segEnd : cp2) : cp1, segStart); 567 segEndAngle = AngleOfVector( 568 segEnd, cp2 == segEnd ? (cp1 == cp2 ? segStart : cp1) : cp2); 569 break; 570 } 571 case StylePathCommand::Tag::QuadCurve: { 572 segEnd = cmd.quad_curve.point.ToGfxPoint() * aZoom; 573 segEnd = cmd.quad_curve.point.IsByCoordinate() 574 ? segEnd + segStart 575 : segEnd; // set before setting tcp2! 576 Point cp1 = 577 cmd.quad_curve.control1.ToGfxPoint(segStart, segEnd) * aZoom; 578 579 prevCP = cp1; 580 segStartAngle = AngleOfVector(cp1 == segStart ? segEnd : cp1, segStart); 581 segEndAngle = AngleOfVector(segEnd, cp1 == segEnd ? segStart : cp1); 582 break; 583 } 584 case StylePathCommand::Tag::Arc: { 585 const auto& arc = cmd.arc; 586 auto radii = arc.radii.ToGfxPoint() * aZoom; 587 float rx = radii.x; 588 float ry = radii.y; 589 float angle = arc.rotate; 590 bool largeArcFlag = arc.arc_size == StyleArcSize::Large; 591 bool sweepFlag = arc.arc_sweep == StyleArcSweep::Cw; 592 segEnd = arc.point.ToGfxPoint() * aZoom; 593 if (arc.point.IsByCoordinate()) { 594 segEnd += segStart; 595 } 596 597 // See section F.6 of SVG 1.1 for details on what we're doing here: 598 // http://www.w3.org/TR/SVG11/implnote.html#ArcImplementationNotes 599 600 if (segStart == segEnd) { 601 // F.6.2 says "If the endpoints (x1, y1) and (x2, y2) are identical, 602 // then this is equivalent to omitting the elliptical arc segment 603 // entirely." We take that very literally here, not adding a mark, and 604 // not even setting any of the 'prev' variables so that it's as if 605 // this arc had never existed; note the difference this will make e.g. 606 // if the arc is proceeded by a bezier curve and followed by a 607 // "smooth" bezier curve of the same degree! 608 continue; 609 } 610 611 // Below we have funny interleaving of F.6.6 (Correction of out-of-range 612 // radii) and F.6.5 (Conversion from endpoint to center 613 // parameterization) which is designed to avoid some unnecessary 614 // calculations. 615 616 if (rx == 0.0 || ry == 0.0) { 617 // F.6.6 step 1 - straight line or coincidental points 618 segStartAngle = segEndAngle = AngleOfVector(segEnd, segStart); 619 break; 620 } 621 622 std::tie(rx, ry, segStartAngle, segEndAngle) = 623 ComputeSegAnglesAndCorrectRadii(segStart, segEnd, angle, 624 largeArcFlag, sweepFlag, rx, ry); 625 break; 626 } 627 case StylePathCommand::Tag::HLine: { 628 const auto x = cmd.h_line.x.ToGfxCoord(); 629 if (cmd.h_line.x.IsToPosition()) { 630 segEnd = Point(x, segStart.y) * aZoom; 631 } else { 632 segEnd = segStart + Point(x, 0.0f) * aZoom; 633 } 634 segStartAngle = segEndAngle = AngleOfVector(segEnd, segStart); 635 break; 636 } 637 case StylePathCommand::Tag::VLine: { 638 const auto y = cmd.v_line.y.ToGfxCoord(); 639 if (cmd.v_line.y.IsToPosition()) { 640 segEnd = Point(segStart.x, y) * aZoom; 641 } else { 642 segEnd = segStart + Point(0.0f, y) * aZoom; 643 } 644 segStartAngle = segEndAngle = AngleOfVector(segEnd, segStart); 645 break; 646 } 647 case StylePathCommand::Tag::SmoothCubic: { 648 const Point& cp1 = prevSeg && prevSeg->IsCubicType() 649 ? segStart * 2 - prevCP 650 : segStart; 651 segEnd = cmd.smooth_cubic.point.ToGfxPoint() * aZoom; 652 segEnd = cmd.smooth_cubic.point.IsByCoordinate() ? segEnd + segStart 653 : segEnd; 654 Point cp2 = 655 cmd.smooth_cubic.control2.ToGfxPoint(segStart, segEnd) * aZoom; 656 657 prevCP = cp2; 658 segStartAngle = AngleOfVector( 659 cp1 == segStart ? (cp1 == cp2 ? segEnd : cp2) : cp1, segStart); 660 segEndAngle = AngleOfVector( 661 segEnd, cp2 == segEnd ? (cp1 == cp2 ? segStart : cp1) : cp2); 662 break; 663 } 664 case StylePathCommand::Tag::SmoothQuad: { 665 const Point& cp1 = prevSeg && prevSeg->IsQuadraticType() 666 ? segStart * 2 - prevCP 667 : segStart; 668 segEnd = cmd.smooth_quad.point.IsToPosition() 669 ? cmd.smooth_quad.point.ToGfxPoint() * aZoom 670 : segStart + cmd.smooth_quad.point.ToGfxPoint() * aZoom; 671 672 prevCP = cp1; 673 segStartAngle = AngleOfVector(cp1 == segStart ? segEnd : cp1, segStart); 674 segEndAngle = AngleOfVector(segEnd, cp1 == segEnd ? segStart : cp1); 675 break; 676 } 677 } 678 679 // Set the angle of the mark at the start of this segment: 680 if (aMarks->Length()) { 681 SVGMark& mark = aMarks->LastElement(); 682 if (!cmd.IsMove() && prevSeg && prevSeg->IsMove()) { 683 // start of new subpath 684 pathStartAngle = mark.angle = segStartAngle; 685 } else if (cmd.IsMove() && !(prevSeg && prevSeg->IsMove())) { 686 // end of a subpath 687 if (!(prevSeg && prevSeg->IsClose())) { 688 mark.angle = prevSegEndAngle; 689 } 690 } else if (!(cmd.IsClose() && prevSeg && prevSeg->IsClose())) { 691 mark.angle = 692 SVGContentUtils::AngleBisect(prevSegEndAngle, segStartAngle); 693 } 694 } 695 696 // Add the mark at the end of this segment, and set its position: 697 // XXX(Bug 1631371) Check if this should use a fallible operation as it 698 // pretended earlier. 699 aMarks->AppendElement(SVGMark(static_cast<float>(segEnd.x), 700 static_cast<float>(segEnd.y), 0.0f, 701 SVGMark::eMid)); 702 703 if (cmd.IsClose() && !(prevSeg && prevSeg->IsClose())) { 704 aMarks->LastElement().angle = aMarks->ElementAt(pathStartIndex).angle = 705 SVGContentUtils::AngleBisect(segEndAngle, pathStartAngle); 706 } 707 708 prevSeg = &cmd; 709 prevSegEnd = segEnd; 710 prevSegEndAngle = segEndAngle; 711 } 712 713 if (!aMarks->IsEmpty()) { 714 if (!(prevSeg && prevSeg->IsClose())) { 715 aMarks->LastElement().angle = prevSegEndAngle; 716 } 717 aMarks->LastElement().type = SVGMark::eEnd; 718 aMarks->ElementAt(0).type = SVGMark::eStart; 719 } 720 } 721 722 size_t SVGPathData::SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const { 723 // TODO: measure mData if unshared? 724 return 0; 725 } 726 727 size_t SVGPathData::SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const { 728 return aMallocSizeOf(this) + SizeOfExcludingThis(aMallocSizeOf); 729 } 730 731 } // namespace mozilla