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/. */
7 #include "SVGPathSegListSMILType.h"
9 #include "mozilla/DebugOnly.h"
10 #include "mozilla/SMILValue.h"
11 #include "SVGPathData.h"
12 #include "SVGPathSegUtils.h"
14 using namespace mozilla::dom::SVGPathSeg_Binding
;
16 // Indices of boolean flags within 'arc' segment chunks in path-data arrays
17 // (where '0' would correspond to the index of the encoded segment type):
18 #define LARGE_ARC_FLAG_IDX 4
19 #define SWEEP_FLAG_IDX 5
23 //----------------------------------------------------------------------
24 // nsISMILType implementation
26 void SVGPathSegListSMILType::Init(SMILValue
& aValue
) const {
27 MOZ_ASSERT(aValue
.IsNull(), "Unexpected value type");
28 aValue
.mU
.mPtr
= new SVGPathDataAndInfo();
32 void SVGPathSegListSMILType::Destroy(SMILValue
& aValue
) const {
33 MOZ_ASSERT(aValue
.mType
== this, "Unexpected SMIL value type");
34 delete static_cast<SVGPathDataAndInfo
*>(aValue
.mU
.mPtr
);
35 aValue
.mU
.mPtr
= nullptr;
36 aValue
.mType
= SMILNullType::Singleton();
39 nsresult
SVGPathSegListSMILType::Assign(SMILValue
& aDest
,
40 const SMILValue
& aSrc
) const {
41 MOZ_ASSERT(aDest
.mType
== aSrc
.mType
, "Incompatible SMIL types");
42 MOZ_ASSERT(aDest
.mType
== this, "Unexpected SMIL value");
44 const SVGPathDataAndInfo
* src
=
45 static_cast<const SVGPathDataAndInfo
*>(aSrc
.mU
.mPtr
);
46 SVGPathDataAndInfo
* dest
= static_cast<SVGPathDataAndInfo
*>(aDest
.mU
.mPtr
);
48 return dest
->CopyFrom(*src
);
51 bool SVGPathSegListSMILType::IsEqual(const SMILValue
& aLeft
,
52 const SMILValue
& aRight
) const {
53 MOZ_ASSERT(aLeft
.mType
== aRight
.mType
, "Incompatible SMIL types");
54 MOZ_ASSERT(aLeft
.mType
== this, "Unexpected type for SMIL value");
56 return *static_cast<const SVGPathDataAndInfo
*>(aLeft
.mU
.mPtr
) ==
57 *static_cast<const SVGPathDataAndInfo
*>(aRight
.mU
.mPtr
);
60 static bool ArcFlagsDiffer(SVGPathDataAndInfo::const_iterator aPathData1
,
61 SVGPathDataAndInfo::const_iterator aPathData2
) {
63 SVGPathSegUtils::IsArcType(SVGPathSegUtils::DecodeType(aPathData1
[0])),
64 "ArcFlagsDiffer called with non-arc segment");
66 SVGPathSegUtils::IsArcType(SVGPathSegUtils::DecodeType(aPathData2
[0])),
67 "ArcFlagsDiffer called with non-arc segment");
69 return aPathData1
[LARGE_ARC_FLAG_IDX
] != aPathData2
[LARGE_ARC_FLAG_IDX
] ||
70 aPathData1
[SWEEP_FLAG_IDX
] != aPathData2
[SWEEP_FLAG_IDX
];
73 enum PathInterpolationResult
{
79 static PathInterpolationResult
CanInterpolate(const SVGPathDataAndInfo
& aStart
,
80 const SVGPathDataAndInfo
& aEnd
) {
81 if (aStart
.IsIdentity()) {
82 return eCanInterpolate
;
85 if (aStart
.Length() != aEnd
.Length()) {
86 return eCannotInterpolate
;
89 PathInterpolationResult result
= eCanInterpolate
;
91 SVGPathDataAndInfo::const_iterator pStart
= aStart
.begin();
92 SVGPathDataAndInfo::const_iterator pEnd
= aEnd
.begin();
93 SVGPathDataAndInfo::const_iterator pStartDataEnd
= aStart
.end();
94 SVGPathDataAndInfo::const_iterator pEndDataEnd
= aEnd
.end();
96 while (pStart
< pStartDataEnd
&& pEnd
< pEndDataEnd
) {
97 uint32_t startType
= SVGPathSegUtils::DecodeType(*pStart
);
98 uint32_t endType
= SVGPathSegUtils::DecodeType(*pEnd
);
100 if (SVGPathSegUtils::IsArcType(startType
) &&
101 SVGPathSegUtils::IsArcType(endType
) && ArcFlagsDiffer(pStart
, pEnd
)) {
102 return eCannotInterpolate
;
105 if (startType
!= endType
) {
106 if (!SVGPathSegUtils::SameTypeModuloRelativeness(startType
, endType
)) {
107 return eCannotInterpolate
;
110 result
= eRequiresConversion
;
113 pStart
+= 1 + SVGPathSegUtils::ArgCountForType(startType
);
114 pEnd
+= 1 + SVGPathSegUtils::ArgCountForType(endType
);
117 MOZ_ASSERT(pStart
<= pStartDataEnd
&& pEnd
<= pEndDataEnd
,
118 "Iterated past end of buffer! (Corrupt path data?)");
120 if (pStart
!= pStartDataEnd
|| pEnd
!= pEndDataEnd
) {
121 return eCannotInterpolate
;
127 enum RelativenessAdjustmentType
{ eAbsoluteToRelative
, eRelativeToAbsolute
};
129 static inline void AdjustSegmentForRelativeness(
130 RelativenessAdjustmentType aAdjustmentType
,
131 const SVGPathDataAndInfo::iterator
& aSegmentToAdjust
,
132 const SVGPathTraversalState
& aState
) {
133 if (aAdjustmentType
== eAbsoluteToRelative
) {
134 aSegmentToAdjust
[0] -= aState
.pos
.x
;
135 aSegmentToAdjust
[1] -= aState
.pos
.y
;
137 aSegmentToAdjust
[0] += aState
.pos
.x
;
138 aSegmentToAdjust
[1] += aState
.pos
.y
;
143 * Helper function for AddWeightedPathSegLists, to add multiples of two
144 * path-segments of the same type.
146 * NOTE: |aSeg1| is allowed to be nullptr, so we use |aSeg2| as the
147 * authoritative source of things like segment-type and boolean arc flags.
149 * @param aCoeff1 The coefficient to use on the first segment.
150 * @param aSeg1 An iterator pointing to the first segment. This can be
151 * null, which is treated as identity (zero).
152 * @param aCoeff2 The coefficient to use on the second segment.
153 * @param aSeg2 An iterator pointing to the second segment.
154 * @param [out] aResultSeg An iterator pointing to where we should write the
155 * result of this operation.
157 static inline void AddWeightedPathSegs(
158 double aCoeff1
, SVGPathDataAndInfo::const_iterator
& aSeg1
, double aCoeff2
,
159 SVGPathDataAndInfo::const_iterator
& aSeg2
,
160 SVGPathDataAndInfo::iterator
& aResultSeg
) {
161 MOZ_ASSERT(aSeg2
, "2nd segment must be non-null");
162 MOZ_ASSERT(aResultSeg
, "result segment must be non-null");
164 uint32_t segType
= SVGPathSegUtils::DecodeType(aSeg2
[0]);
165 MOZ_ASSERT(!aSeg1
|| SVGPathSegUtils::DecodeType(*aSeg1
) == segType
,
166 "unexpected segment type");
168 // FIRST: Directly copy the arguments that don't make sense to add.
169 aResultSeg
[0] = aSeg2
[0]; // encoded segment type
171 bool isArcType
= SVGPathSegUtils::IsArcType(segType
);
173 // Copy boolean arc flags.
174 MOZ_ASSERT(!aSeg1
|| !ArcFlagsDiffer(aSeg1
, aSeg2
),
175 "Expecting arc flags to match");
176 aResultSeg
[LARGE_ARC_FLAG_IDX
] = aSeg2
[LARGE_ARC_FLAG_IDX
];
177 aResultSeg
[SWEEP_FLAG_IDX
] = aSeg2
[SWEEP_FLAG_IDX
];
180 // SECOND: Add the arguments that are supposed to be added.
181 // (The 1's below are to account for segment type)
182 uint32_t numArgs
= SVGPathSegUtils::ArgCountForType(segType
);
183 for (uint32_t i
= 1; i
< 1 + numArgs
; ++i
) {
184 // Need to skip arc flags for arc-type segments. (already handled them)
185 if (!(isArcType
&& (i
== LARGE_ARC_FLAG_IDX
|| i
== SWEEP_FLAG_IDX
))) {
186 aResultSeg
[i
] = (aSeg1
? aCoeff1
* aSeg1
[i
] : 0.0) + aCoeff2
* aSeg2
[i
];
190 // FINALLY: Shift iterators forward. ("1+" is to include seg-type)
192 aSeg1
+= 1 + numArgs
;
194 aSeg2
+= 1 + numArgs
;
195 aResultSeg
+= 1 + numArgs
;
199 * Helper function for Add & Interpolate, to add multiples of two path-segment
202 * NOTE: aList1 and aList2 are assumed to have their segment-types and
203 * segment-count match exactly (unless aList1 is an identity value).
205 * NOTE: aResult, the output list, is expected to either be an identity value
206 * (in which case we'll grow it) *or* to already have the exactly right length
207 * (e.g. in cases where aList1 and aResult are actually the same list).
209 * @param aCoeff1 The coefficient to use on the first path segment list.
210 * @param aList1 The first path segment list. Allowed to be identity.
211 * @param aCoeff2 The coefficient to use on the second path segment list.
212 * @param aList2 The second path segment list.
213 * @param [out] aResultSeg The resulting path segment list. Allowed to be
214 * identity, in which case we'll grow it to the right
215 * size. Also allowed to be the same list as aList1.
217 static nsresult
AddWeightedPathSegLists(double aCoeff1
,
218 const SVGPathDataAndInfo
& aList1
,
220 const SVGPathDataAndInfo
& aList2
,
221 SVGPathDataAndInfo
& aResult
) {
222 MOZ_ASSERT(aCoeff1
>= 0.0 && aCoeff2
>= 0.0,
223 "expecting non-negative coefficients");
224 MOZ_ASSERT(!aList2
.IsIdentity(), "expecting 2nd list to be non-identity");
225 MOZ_ASSERT(aList1
.IsIdentity() || aList1
.Length() == aList2
.Length(),
226 "expecting 1st list to be identity or to have same "
227 "length as 2nd list");
228 MOZ_ASSERT(aResult
.IsIdentity() || aResult
.Length() == aList2
.Length(),
229 "expecting result list to be identity or to have same "
230 "length as 2nd list");
232 SVGPathDataAndInfo::const_iterator iter1
, end1
;
233 if (aList1
.IsIdentity()) {
234 iter1
= end1
= nullptr; // indicate that this is an identity list
236 iter1
= aList1
.begin();
239 SVGPathDataAndInfo::const_iterator iter2
= aList2
.begin();
240 SVGPathDataAndInfo::const_iterator end2
= aList2
.end();
242 // Grow |aResult| if necessary. (NOTE: It's possible that aResult and aList1
243 // are the same list, so this may implicitly resize aList1. That's fine,
244 // because in that case, we will have already set iter1 to nullptr above, to
245 // record that our first operand is an identity value.)
246 if (aResult
.IsIdentity()) {
247 if (!aResult
.SetLength(aList2
.Length())) {
248 return NS_ERROR_OUT_OF_MEMORY
;
250 aResult
.SetElement(aList2
.Element()); // propagate target element info!
253 SVGPathDataAndInfo::iterator resultIter
= aResult
.begin();
255 while ((!iter1
|| iter1
!= end1
) && iter2
!= end2
) {
256 AddWeightedPathSegs(aCoeff1
, iter1
, aCoeff2
, iter2
, resultIter
);
259 (!iter1
|| iter1
== end1
) && iter2
== end2
&& resultIter
== aResult
.end(),
260 "Very, very bad - path data corrupt");
264 static void ConvertPathSegmentData(SVGPathDataAndInfo::const_iterator
& aStart
,
265 SVGPathDataAndInfo::const_iterator
& aEnd
,
266 SVGPathDataAndInfo::iterator
& aResult
,
267 SVGPathTraversalState
& aState
) {
268 uint32_t startType
= SVGPathSegUtils::DecodeType(*aStart
);
269 uint32_t endType
= SVGPathSegUtils::DecodeType(*aEnd
);
271 uint32_t segmentLengthIncludingType
=
272 1 + SVGPathSegUtils::ArgCountForType(startType
);
274 SVGPathDataAndInfo::const_iterator pResultSegmentBegin
= aResult
;
276 if (startType
== endType
) {
277 // No conversion need, just directly copy aStart.
278 aEnd
+= segmentLengthIncludingType
;
279 while (segmentLengthIncludingType
) {
280 *aResult
++ = *aStart
++;
281 --segmentLengthIncludingType
;
283 SVGPathSegUtils::TraversePathSegment(pResultSegmentBegin
, aState
);
288 SVGPathSegUtils::SameTypeModuloRelativeness(startType
, endType
),
289 "Incompatible path segment types passed to ConvertPathSegmentData!");
291 RelativenessAdjustmentType adjustmentType
=
292 SVGPathSegUtils::IsRelativeType(startType
) ? eRelativeToAbsolute
293 : eAbsoluteToRelative
;
296 segmentLengthIncludingType
==
297 1 + SVGPathSegUtils::ArgCountForType(endType
),
298 "Compatible path segment types for interpolation had different lengths!");
300 aResult
[0] = aEnd
[0];
303 case PATHSEG_LINETO_HORIZONTAL_ABS
:
304 case PATHSEG_LINETO_HORIZONTAL_REL
:
307 (adjustmentType
== eRelativeToAbsolute
? 1 : -1) * aState
.pos
.x
;
309 case PATHSEG_LINETO_VERTICAL_ABS
:
310 case PATHSEG_LINETO_VERTICAL_REL
:
313 (adjustmentType
== eRelativeToAbsolute
? 1 : -1) * aState
.pos
.y
;
315 case PATHSEG_ARC_ABS
:
316 case PATHSEG_ARC_REL
:
317 aResult
[1] = aStart
[1];
318 aResult
[2] = aStart
[2];
319 aResult
[3] = aStart
[3];
320 aResult
[4] = aStart
[4];
321 aResult
[5] = aStart
[5];
322 aResult
[6] = aStart
[6];
323 aResult
[7] = aStart
[7];
324 AdjustSegmentForRelativeness(adjustmentType
, aResult
+ 6, aState
);
326 case PATHSEG_CURVETO_CUBIC_ABS
:
327 case PATHSEG_CURVETO_CUBIC_REL
:
328 aResult
[5] = aStart
[5];
329 aResult
[6] = aStart
[6];
330 AdjustSegmentForRelativeness(adjustmentType
, aResult
+ 5, aState
);
332 case PATHSEG_CURVETO_QUADRATIC_ABS
:
333 case PATHSEG_CURVETO_QUADRATIC_REL
:
334 case PATHSEG_CURVETO_CUBIC_SMOOTH_ABS
:
335 case PATHSEG_CURVETO_CUBIC_SMOOTH_REL
:
336 aResult
[3] = aStart
[3];
337 aResult
[4] = aStart
[4];
338 AdjustSegmentForRelativeness(adjustmentType
, aResult
+ 3, aState
);
340 case PATHSEG_MOVETO_ABS
:
341 case PATHSEG_MOVETO_REL
:
342 case PATHSEG_LINETO_ABS
:
343 case PATHSEG_LINETO_REL
:
344 case PATHSEG_CURVETO_QUADRATIC_SMOOTH_ABS
:
345 case PATHSEG_CURVETO_QUADRATIC_SMOOTH_REL
:
346 aResult
[1] = aStart
[1];
347 aResult
[2] = aStart
[2];
348 AdjustSegmentForRelativeness(adjustmentType
, aResult
+ 1, aState
);
352 SVGPathSegUtils::TraversePathSegment(pResultSegmentBegin
, aState
);
353 aStart
+= segmentLengthIncludingType
;
354 aEnd
+= segmentLengthIncludingType
;
355 aResult
+= segmentLengthIncludingType
;
358 static void ConvertAllPathSegmentData(
359 SVGPathDataAndInfo::const_iterator aStart
,
360 SVGPathDataAndInfo::const_iterator aStartDataEnd
,
361 SVGPathDataAndInfo::const_iterator aEnd
,
362 SVGPathDataAndInfo::const_iterator aEndDataEnd
,
363 SVGPathDataAndInfo::iterator aResult
) {
364 SVGPathTraversalState state
;
365 state
.mode
= SVGPathTraversalState::eUpdateOnlyStartAndCurrentPos
;
366 while (aStart
< aStartDataEnd
&& aEnd
< aEndDataEnd
) {
367 ConvertPathSegmentData(aStart
, aEnd
, aResult
, state
);
369 MOZ_ASSERT(aStart
== aStartDataEnd
&& aEnd
== aEndDataEnd
,
370 "Failed to convert all path segment data! (Corrupt?)");
373 nsresult
SVGPathSegListSMILType::Add(SMILValue
& aDest
,
374 const SMILValue
& aValueToAdd
,
375 uint32_t aCount
) const {
376 MOZ_ASSERT(aDest
.mType
== this, "Unexpected SMIL type");
377 MOZ_ASSERT(aValueToAdd
.mType
== this, "Incompatible SMIL type");
379 SVGPathDataAndInfo
& dest
= *static_cast<SVGPathDataAndInfo
*>(aDest
.mU
.mPtr
);
380 const SVGPathDataAndInfo
& valueToAdd
=
381 *static_cast<const SVGPathDataAndInfo
*>(aValueToAdd
.mU
.mPtr
);
383 if (valueToAdd
.IsIdentity()) { // Adding identity value - no-op
387 if (!dest
.IsIdentity()) {
388 // Neither value is identity; make sure they're compatible.
389 MOZ_ASSERT(dest
.Element() == valueToAdd
.Element(),
390 "adding values from different elements...?");
392 PathInterpolationResult check
= CanInterpolate(dest
, valueToAdd
);
393 if (check
== eCannotInterpolate
) {
394 // SVGContentUtils::ReportToConsole - can't add path segment lists with
395 // different numbers of segments, with arcs that have different flag
396 // values, or with incompatible segment types.
397 return NS_ERROR_FAILURE
;
399 if (check
== eRequiresConversion
) {
400 // Convert dest, in-place, to match the types in valueToAdd:
401 ConvertAllPathSegmentData(dest
.begin(), dest
.end(), valueToAdd
.begin(),
402 valueToAdd
.end(), dest
.begin());
406 return AddWeightedPathSegLists(1.0, dest
, aCount
, valueToAdd
, dest
);
409 nsresult
SVGPathSegListSMILType::ComputeDistance(const SMILValue
& aFrom
,
410 const SMILValue
& aTo
,
411 double& aDistance
) const {
412 MOZ_ASSERT(aFrom
.mType
== this, "Unexpected SMIL type");
413 MOZ_ASSERT(aTo
.mType
== this, "Incompatible SMIL type");
415 // See https://bugzilla.mozilla.org/show_bug.cgi?id=522306#c18
417 // SVGContentUtils::ReportToConsole
418 return NS_ERROR_NOT_IMPLEMENTED
;
421 nsresult
SVGPathSegListSMILType::Interpolate(const SMILValue
& aStartVal
,
422 const SMILValue
& aEndVal
,
423 double aUnitDistance
,
424 SMILValue
& aResult
) const {
425 MOZ_ASSERT(aStartVal
.mType
== aEndVal
.mType
,
426 "Trying to interpolate different types");
427 MOZ_ASSERT(aStartVal
.mType
== this, "Unexpected types for interpolation");
428 MOZ_ASSERT(aResult
.mType
== this, "Unexpected result type");
430 const SVGPathDataAndInfo
& start
=
431 *static_cast<const SVGPathDataAndInfo
*>(aStartVal
.mU
.mPtr
);
432 const SVGPathDataAndInfo
& end
=
433 *static_cast<const SVGPathDataAndInfo
*>(aEndVal
.mU
.mPtr
);
434 SVGPathDataAndInfo
& result
=
435 *static_cast<SVGPathDataAndInfo
*>(aResult
.mU
.mPtr
);
436 MOZ_ASSERT(result
.IsIdentity(),
437 "expecting outparam to start out as identity");
439 PathInterpolationResult check
= CanInterpolate(start
, end
);
441 if (check
== eCannotInterpolate
) {
442 // SVGContentUtils::ReportToConsole - can't interpolate path segment lists
443 // with different numbers of segments, with arcs with different flag values,
444 // or with incompatible segment types.
445 return NS_ERROR_FAILURE
;
448 const SVGPathDataAndInfo
* startListToUse
= &start
;
449 if (check
== eRequiresConversion
) {
450 // Can't convert |start| in-place, since it's const. Instead, we copy it
451 // into |result|, converting the types as we go, and use that as our start.
452 if (!result
.SetLength(end
.Length())) {
453 return NS_ERROR_OUT_OF_MEMORY
;
455 result
.SetElement(end
.Element()); // propagate target element info!
457 ConvertAllPathSegmentData(start
.begin(), start
.end(), end
.begin(),
458 end
.end(), result
.begin());
459 startListToUse
= &result
;
462 return AddWeightedPathSegLists(1.0 - aUnitDistance
, *startListToUse
,
463 aUnitDistance
, end
, result
);
466 } // namespace mozilla