Bug 1874684 - Part 4: Prefer const references instead of copying Instant values....
[gecko.git] / dom / svg / SVGPathSegListSMILType.cpp
blob613e1bc83d5c710082da22752d68ad15333e82a2
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
21 namespace mozilla {
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();
29 aValue.mType = this;
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) {
62 MOZ_ASSERT(
63 SVGPathSegUtils::IsArcType(SVGPathSegUtils::DecodeType(aPathData1[0])),
64 "ArcFlagsDiffer called with non-arc segment");
65 MOZ_ASSERT(
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 {
74 eCannotInterpolate,
75 eRequiresConversion,
76 eCanInterpolate
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;
124 return result;
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;
136 } else {
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);
172 if (isArcType) {
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)
191 if (aSeg1) {
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
200 * lists.
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,
219 double aCoeff2,
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
235 } else {
236 iter1 = aList1.begin();
237 end1 = aList1.end();
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);
258 MOZ_ASSERT(
259 (!iter1 || iter1 == end1) && iter2 == end2 && resultIter == aResult.end(),
260 "Very, very bad - path data corrupt");
261 return NS_OK;
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);
284 return;
287 MOZ_ASSERT(
288 SVGPathSegUtils::SameTypeModuloRelativeness(startType, endType),
289 "Incompatible path segment types passed to ConvertPathSegmentData!");
291 RelativenessAdjustmentType adjustmentType =
292 SVGPathSegUtils::IsRelativeType(startType) ? eRelativeToAbsolute
293 : eAbsoluteToRelative;
295 MOZ_ASSERT(
296 segmentLengthIncludingType ==
297 1 + SVGPathSegUtils::ArgCountForType(endType),
298 "Compatible path segment types for interpolation had different lengths!");
300 aResult[0] = aEnd[0];
302 switch (endType) {
303 case PATHSEG_LINETO_HORIZONTAL_ABS:
304 case PATHSEG_LINETO_HORIZONTAL_REL:
305 aResult[1] =
306 aStart[1] +
307 (adjustmentType == eRelativeToAbsolute ? 1 : -1) * aState.pos.x;
308 break;
309 case PATHSEG_LINETO_VERTICAL_ABS:
310 case PATHSEG_LINETO_VERTICAL_REL:
311 aResult[1] =
312 aStart[1] +
313 (adjustmentType == eRelativeToAbsolute ? 1 : -1) * aState.pos.y;
314 break;
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);
325 break;
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);
331 [[fallthrough]];
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);
339 [[fallthrough]];
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);
349 break;
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
384 return NS_OK;
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